Skip to content

Commit

Permalink
PMM-12251 Endpoint to current service account GRAFANA 10. (#703)
Browse files Browse the repository at this point in the history
* PMM-12251 Initial changes to v3.

* Update pkg/services/serviceaccounts/database/store.go

Co-authored-by: Alex Demidoff <[email protected]>

* PMM-12251 Remove our API key migration code.

* PMM-12251 CurrentServiceAccount endpoint works only with service token.

* PMM-12251 Better error check.

* PMM-12251 Revert last changes in MigrateApiKey method.

* PMM-12251 Remove empty line.

* Update pkg/services/serviceaccounts/api/token.go

Co-authored-by: Michael Okoko <[email protected]>

* Update pkg/services/apikey/model.go

Co-authored-by: Michael Okoko <[email protected]>

* PMM-12251 Changes after suggestions.

* PMM-12251 Logic fix after changes.

* PMM-12251 Error check with login name.

* Revert "PMM-12251 Error check with login name."

This reverts commit 1811a72.

---------

Co-authored-by: Alex Demidoff <[email protected]>
Co-authored-by: Michael Okoko <[email protected]>
  • Loading branch information
3 people authored Apr 1, 2024
1 parent f283b87 commit a737f88
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 10 deletions.
2 changes: 1 addition & 1 deletion pkg/api/dtos/apikey.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type NewApiKeyResult struct {
ID int64 `json:"id"`
// example: grafana
Name string `json:"name"`
// example: glsa_yscW25imSKJIuav8zF37RZmnbiDvB05G_fcaaf58a
// example: eyJrIjoiWDZTTmtVMjZmcDNWNDA3dE43bGJoVEd2NDFmRkVpMVoiLCJuIjoidGVzdCIsImlkIjoxfQ==a
Key string `json:"key"`
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/components/apikeygen/apikeygen.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/grafana/grafana/pkg/util"
)

var ErrInvalidApiKey = errors.New("invalid API key")
var ErrInvalidApiKey = errors.New("invalid Service Token/API key")

type KeyGenResult struct {
HashedKey string
Expand Down
6 changes: 3 additions & 3 deletions pkg/services/apikey/apikeyimpl/xorm_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func (ss *sqlStore) GetApiKeyById(ctx context.Context, query *apikey.GetByIDQuer
if err != nil {
return err
} else if !has {
return apikey.ErrInvalid
return apikey.ErrInvalidAuth
}

res = &key
Expand All @@ -161,7 +161,7 @@ func (ss *sqlStore) GetApiKeyByName(ctx context.Context, query *apikey.GetByName
if err != nil {
return err
} else if !has {
return apikey.ErrInvalid
return apikey.ErrInvalidAuth
}

res = &key
Expand All @@ -177,7 +177,7 @@ func (ss *sqlStore) GetAPIKeyByHash(ctx context.Context, hash string) (*apikey.A
if err != nil {
return err
} else if !has {
return apikey.ErrInvalid
return apikey.ErrInvalidAuth
}
return nil
})
Expand Down
2 changes: 1 addition & 1 deletion pkg/services/apikey/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

var (
ErrNotFound = errors.New("API key not found")
ErrInvalid = errors.New("invalid API key")
ErrInvalidAuth = errors.New("invalid Service Token/API key")
ErrInvalidExpiration = errors.New("negative value for SecondsToLive")
ErrDuplicate = errors.New("API key, organization ID and name must be unique")
)
Expand Down
6 changes: 6 additions & 0 deletions pkg/services/serviceaccounts/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ func (api *ServiceAccountsAPI) RegisterAPIEndpoints() {
serviceAccountsRoute.Post("/migrate", auth(accesscontrol.EvalPermission(serviceaccounts.ActionCreate)), routing.Wrap(api.MigrateApiKeysToServiceAccounts))
serviceAccountsRoute.Post("/migrate/:keyId", auth(accesscontrol.EvalPermission(serviceaccounts.ActionCreate)), routing.Wrap(api.ConvertToServiceAccount))
}, requestmeta.SetOwner(requestmeta.TeamAuth))

// @PERCONA
// current service account (works only with service account token auth). If you use API key then key will be automatically migrated into service account.
api.RouterRegister.Group("/api/auth/serviceaccount", func(serviceAccountsRoute routing.RouteRegister) {
serviceAccountsRoute.Get("/", routing.Wrap(api.CurrentServiceAccount))
})
}

// swagger:route POST /serviceaccounts service_accounts createServiceAccount
Expand Down
32 changes: 32 additions & 0 deletions pkg/services/serviceaccounts/api/token.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package api

import (
"errors"
"net/http"
"strconv"
"time"
Expand Down Expand Up @@ -110,6 +111,37 @@ func (api *ServiceAccountsAPI) ListTokens(ctx *contextmodel.ReqContext) response
return response.JSON(http.StatusOK, result)
}

// @PERCONA
// swagger:route GET /auth/serviceaccount serviceaccounts retrieveServiceAccount
//
// # CurrentServiceAccount get current service account info
//
// Requires service account token authentication.
//
// Responses:
// 200: retrieveServiceAccountResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
func (api *ServiceAccountsAPI) CurrentServiceAccount(ctx *contextmodel.ReqContext) response.Response {
if !ctx.IsServiceAccount {
return response.Error(http.StatusBadRequest, "Auth method is not service account token", errors.New("failed to get service account info"))
}

serviceAccount, err := api.service.RetrieveServiceAccount(ctx.Req.Context(), ctx.OrgID, ctx.UserID)
if err != nil {
switch {
case errors.Is(err, serviceaccounts.ErrServiceAccountNotFound):
return response.Error(http.StatusNotFound, "Failed to find service account", err)
default:
return response.Error(http.StatusInternalServerError, "Failed to retrieve service account", err)
}
}

return response.JSON(http.StatusOK, serviceAccount)
}

// swagger:route POST /serviceaccounts/{serviceAccountId}/tokens service_accounts createToken
//
// # CreateNewToken adds a token to a service account
Expand Down
42 changes: 38 additions & 4 deletions pkg/services/serviceaccounts/database/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package database
//nolint:goimports
import (
"context"
"errors"
"fmt"
"strings"
"time"
Expand Down Expand Up @@ -44,6 +45,7 @@ func ProvideServiceAccountsStore(cfg *setting.Cfg, store db.DB, apiKeyService ap

// CreateServiceAccount creates service account
func (s *ServiceAccountsStoreImpl) CreateServiceAccount(ctx context.Context, orgId int64, saForm *serviceaccounts.CreateServiceAccountForm) (*serviceaccounts.ServiceAccountDTO, error) {
name := saForm.Name
generatedLogin := serviceaccounts.ServiceAccountPrefix + strings.ToLower(saForm.Name)
generatedLogin = strings.ReplaceAll(generatedLogin, " ", "-")
isDisabled := false
Expand All @@ -55,16 +57,48 @@ func (s *ServiceAccountsStoreImpl) CreateServiceAccount(ctx context.Context, org
role = *saForm.Role
}

force := false
if saForm.Force != nil {
force = *saForm.Force
}

newSA, err := s.userService.CreateServiceAccount(ctx, &user.CreateUserCommand{
Login: generatedLogin,
OrgID: orgId,
Name: saForm.Name,
Name: name,
IsDisabled: isDisabled,
IsServiceAccount: true,
DefaultOrgRole: string(role),
})
if err != nil {
return nil, fmt.Errorf("failed to create service account: %w", err)
if errors.Is(err, serviceaccounts.ErrServiceAccountAlreadyExists) && force {
serviceAccountID, err := s.RetrieveServiceAccountIdByName(ctx, orgId, name)
if err != nil {
return nil, err
}

updateForm := &serviceaccounts.UpdateServiceAccountForm{
Name: &name,
Role: &role,
IsDisabled: &isDisabled,
}
updatedAccount, err := s.UpdateServiceAccount(ctx, orgId, serviceAccountID, updateForm)
if err != nil {
return nil, err
}

return &serviceaccounts.ServiceAccountDTO{
Id: updatedAccount.Id,
Name: updatedAccount.Name,
Login: updatedAccount.Login,
OrgId: updatedAccount.OrgId,
Tokens: updatedAccount.Tokens,
Role: updatedAccount.Role,
IsDisabled: updatedAccount.IsDisabled,
}, nil
} else {
return nil, fmt.Errorf("failed to create service account: %w", err)
}
}

return &serviceaccounts.ServiceAccountDTO{
Expand Down Expand Up @@ -238,7 +272,7 @@ func (s *ServiceAccountsStoreImpl) RetrieveServiceAccountIdByName(ctx context.Co
sess := dbSession.Table("user")

whereConditions := []string{
fmt.Sprintf("%s.name = ?",
fmt.Sprintf("LOWER(%s.name) = LOWER(?)",
s.sqlStore.GetDialect().Quote("user")),
fmt.Sprintf("%s.org_id = ?",
s.sqlStore.GetDialect().Quote("user")),
Expand Down Expand Up @@ -399,7 +433,7 @@ func (s *ServiceAccountsStoreImpl) MigrateApiKeysToServiceAccounts(ctx context.C
for _, key := range basicKeys {
err := s.CreateServiceAccountFromApikey(ctx, key)
if err != nil {
s.log.Error("Migating to service accounts failed with error", err.Error())
s.log.Error("Migrating to service accounts failed with error", err.Error())
migrationResult.Failed++
migrationResult.FailedDetails = append(migrationResult.FailedDetails, fmt.Sprintf("API key name: %s - Error: %s", key.Name, err.Error()))
migrationResult.FailedApikeyIDs = append(migrationResult.FailedApikeyIDs, key.ID)
Expand Down
2 changes: 2 additions & 0 deletions pkg/services/serviceaccounts/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ type CreateServiceAccountForm struct {
Role *org.RoleType `json:"role"`
// example: false
IsDisabled *bool `json:"isDisabled"`
// example: false
Force *bool `json:"force"`
}

// swagger:model
Expand Down

0 comments on commit a737f88

Please sign in to comment.