mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
auth: add serviceaccount proxy (#76815)
* Add proxy service template * Replace SA srv with proxy for external SA srv * Move service account prefix to a constant * Prevent deletion from external service account * Make SA validation a resusable function * Add protection for creating service accounts * Add protection when updating service accounts * Add IsExternal field for service account * Protect ext service account token generation * Add verbose errors for form name or sa name * add tests * Add logs * Adjusts tests --------- Co-authored-by: Misi <mgyongyosi@users.noreply.github.com> Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
This commit is contained in:
@@ -3,15 +3,23 @@ package extsvcaccounts
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/models/roletype"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
|
||||
const (
|
||||
extsvcPrefix = "extsvc-"
|
||||
ExtSvcPrefix = "extsvc-"
|
||||
kvStoreType = "extsvc-token"
|
||||
// #nosec G101 - this is not a hardcoded secret
|
||||
tokenNamePrefix = "extsvc-token"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCannotBeDeleted = errutil.BadRequest("extsvcaccounts.ErrCannotBeDeleted", errutil.WithPublicMessage("external service account cannot be deleted"))
|
||||
ErrInvalidName = errutil.BadRequest("extsvcaccounts.ErrInvalidName", errutil.WithPublicMessage("only external service account names can be prefixed with 'extsvc-'"))
|
||||
ErrCannotBeUpdated = errutil.BadRequest("extsvcaccounts.ErrCannotBeUpdated", errutil.WithPublicMessage("external service account cannot be updated"))
|
||||
ErrCannotCreateToken = errutil.BadRequest("extsvcaccounts.ErrCannotCreateToken", errutil.WithPublicMessage("cannot add external service account token"))
|
||||
)
|
||||
|
||||
// Credentials represents the credentials associated to an external service
|
||||
type Credentials struct {
|
||||
Secret string
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
||||
sa "github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts/manager"
|
||||
)
|
||||
|
||||
type ExtSvcAccountsService struct {
|
||||
@@ -23,7 +24,7 @@ type ExtSvcAccountsService struct {
|
||||
skvStore kvstore.SecretsKVStore
|
||||
}
|
||||
|
||||
func ProvideExtSvcAccountsService(acSvc ac.Service, saSvc sa.Service, db db.DB, secretsSvc secrets.Service) *ExtSvcAccountsService {
|
||||
func ProvideExtSvcAccountsService(acSvc ac.Service, saSvc *manager.ServiceAccountsService, db db.DB, secretsSvc secrets.Service) *ExtSvcAccountsService {
|
||||
logger := log.New("serviceauth.extsvcaccounts")
|
||||
return &ExtSvcAccountsService{
|
||||
acSvc: acSvc,
|
||||
@@ -96,7 +97,7 @@ func (esa *ExtSvcAccountsService) ManageExtSvcAccount(ctx context.Context, cmd *
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
saID, errRetrieve := esa.saSvc.RetrieveServiceAccountIdByName(ctx, cmd.OrgID, extsvcPrefix+cmd.ExtSvcSlug)
|
||||
saID, errRetrieve := esa.saSvc.RetrieveServiceAccountIdByName(ctx, cmd.OrgID, ExtSvcPrefix+cmd.ExtSvcSlug)
|
||||
if errRetrieve != nil && !errors.Is(errRetrieve, sa.ErrServiceAccountNotFound) {
|
||||
return 0, errRetrieve
|
||||
}
|
||||
@@ -139,7 +140,7 @@ func (esa *ExtSvcAccountsService) saveExtSvcAccount(ctx context.Context, cmd *sa
|
||||
// Create a service account
|
||||
esa.logger.Debug("Create service account", "service", cmd.ExtSvcSlug, "orgID", cmd.OrgID)
|
||||
sa, err := esa.saSvc.CreateServiceAccount(ctx, cmd.OrgID, &sa.CreateServiceAccountForm{
|
||||
Name: extsvcPrefix + cmd.ExtSvcSlug,
|
||||
Name: ExtSvcPrefix + cmd.ExtSvcSlug,
|
||||
Role: newRole(roletype.RoleNone),
|
||||
IsDisabled: newBool(false),
|
||||
})
|
||||
|
||||
@@ -87,7 +87,7 @@ func TestExtSvcAccountsService_ManageExtSvcAccount(t *testing.T) {
|
||||
checks: func(t *testing.T, env *TestEnv) {
|
||||
env.SaSvc.AssertCalled(t, "RetrieveServiceAccountIdByName", mock.Anything,
|
||||
mock.MatchedBy(func(orgID int64) bool { return orgID == extSvcOrgID }),
|
||||
mock.MatchedBy(func(slug string) bool { return slug == extsvcPrefix+extSvcSlug }))
|
||||
mock.MatchedBy(func(slug string) bool { return slug == ExtSvcPrefix+extSvcSlug }))
|
||||
env.SaSvc.AssertCalled(t, "DeleteServiceAccount", mock.Anything,
|
||||
mock.MatchedBy(func(orgID int64) bool { return orgID == extSvcOrgID }),
|
||||
mock.MatchedBy(func(saID int64) bool { return saID == extSvcAccID }))
|
||||
@@ -114,7 +114,7 @@ func TestExtSvcAccountsService_ManageExtSvcAccount(t *testing.T) {
|
||||
checks: func(t *testing.T, env *TestEnv) {
|
||||
env.SaSvc.AssertCalled(t, "RetrieveServiceAccountIdByName", mock.Anything,
|
||||
mock.MatchedBy(func(orgID int64) bool { return orgID == extSvcOrgID }),
|
||||
mock.MatchedBy(func(slug string) bool { return slug == extsvcPrefix+extSvcSlug }))
|
||||
mock.MatchedBy(func(slug string) bool { return slug == ExtSvcPrefix+extSvcSlug }))
|
||||
env.SaSvc.AssertCalled(t, "DeleteServiceAccount", mock.Anything,
|
||||
mock.MatchedBy(func(orgID int64) bool { return orgID == extSvcOrgID }),
|
||||
mock.MatchedBy(func(saID int64) bool { return saID == extSvcAccID }))
|
||||
@@ -143,11 +143,11 @@ func TestExtSvcAccountsService_ManageExtSvcAccount(t *testing.T) {
|
||||
checks: func(t *testing.T, env *TestEnv) {
|
||||
env.SaSvc.AssertCalled(t, "RetrieveServiceAccountIdByName", mock.Anything,
|
||||
mock.MatchedBy(func(orgID int64) bool { return orgID == extSvcOrgID }),
|
||||
mock.MatchedBy(func(slug string) bool { return slug == extsvcPrefix+extSvcSlug }))
|
||||
mock.MatchedBy(func(slug string) bool { return slug == ExtSvcPrefix+extSvcSlug }))
|
||||
env.SaSvc.AssertCalled(t, "CreateServiceAccount", mock.Anything,
|
||||
mock.MatchedBy(func(orgID int64) bool { return orgID == extSvcOrgID }),
|
||||
mock.MatchedBy(func(cmd *sa.CreateServiceAccountForm) bool {
|
||||
return cmd.Name == extsvcPrefix+extSvcSlug && *cmd.Role == roletype.RoleNone
|
||||
return cmd.Name == ExtSvcPrefix+extSvcSlug && *cmd.Role == roletype.RoleNone
|
||||
}),
|
||||
)
|
||||
env.AcStore.AssertCalled(t, "SaveExternalServiceRole", mock.Anything,
|
||||
@@ -177,7 +177,7 @@ func TestExtSvcAccountsService_ManageExtSvcAccount(t *testing.T) {
|
||||
checks: func(t *testing.T, env *TestEnv) {
|
||||
env.SaSvc.AssertCalled(t, "RetrieveServiceAccountIdByName", mock.Anything,
|
||||
mock.MatchedBy(func(orgID int64) bool { return orgID == extSvcOrgID }),
|
||||
mock.MatchedBy(func(slug string) bool { return slug == extsvcPrefix+extSvcSlug }))
|
||||
mock.MatchedBy(func(slug string) bool { return slug == ExtSvcPrefix+extSvcSlug }))
|
||||
env.AcStore.AssertCalled(t, "SaveExternalServiceRole", mock.Anything,
|
||||
mock.MatchedBy(func(cmd ac.SaveExternalServiceRoleCommand) bool {
|
||||
return cmd.ServiceAccountID == int64(11) && cmd.ExternalServiceID == extSvcSlug &&
|
||||
@@ -257,7 +257,7 @@ func TestExtSvcAccountsService_SaveExternalService(t *testing.T) {
|
||||
checks: func(t *testing.T, env *TestEnv) {
|
||||
env.SaSvc.AssertCalled(t, "RetrieveServiceAccountIdByName", mock.Anything,
|
||||
mock.MatchedBy(func(orgID int64) bool { return orgID == tmpOrgID }),
|
||||
mock.MatchedBy(func(slug string) bool { return slug == extsvcPrefix+extSvcSlug }))
|
||||
mock.MatchedBy(func(slug string) bool { return slug == ExtSvcPrefix+extSvcSlug }))
|
||||
env.SaSvc.AssertCalled(t, "DeleteServiceAccount", mock.Anything,
|
||||
mock.MatchedBy(func(orgID int64) bool { return orgID == tmpOrgID }),
|
||||
mock.MatchedBy(func(saID int64) bool { return saID == extSvcAccID }))
|
||||
@@ -287,7 +287,7 @@ func TestExtSvcAccountsService_SaveExternalService(t *testing.T) {
|
||||
checks: func(t *testing.T, env *TestEnv) {
|
||||
env.SaSvc.AssertCalled(t, "RetrieveServiceAccountIdByName", mock.Anything,
|
||||
mock.MatchedBy(func(orgID int64) bool { return orgID == tmpOrgID }),
|
||||
mock.MatchedBy(func(slug string) bool { return slug == extsvcPrefix+extSvcSlug }))
|
||||
mock.MatchedBy(func(slug string) bool { return slug == ExtSvcPrefix+extSvcSlug }))
|
||||
env.SaSvc.AssertCalled(t, "DeleteServiceAccount", mock.Anything,
|
||||
mock.MatchedBy(func(orgID int64) bool { return orgID == tmpOrgID }),
|
||||
mock.MatchedBy(func(saID int64) bool { return saID == extSvcAccID }))
|
||||
@@ -319,11 +319,11 @@ func TestExtSvcAccountsService_SaveExternalService(t *testing.T) {
|
||||
checks: func(t *testing.T, env *TestEnv) {
|
||||
env.SaSvc.AssertCalled(t, "RetrieveServiceAccountIdByName", mock.Anything,
|
||||
mock.MatchedBy(func(orgID int64) bool { return orgID == tmpOrgID }),
|
||||
mock.MatchedBy(func(slug string) bool { return slug == extsvcPrefix+extSvcSlug }))
|
||||
mock.MatchedBy(func(slug string) bool { return slug == ExtSvcPrefix+extSvcSlug }))
|
||||
env.SaSvc.AssertCalled(t, "CreateServiceAccount", mock.Anything,
|
||||
mock.MatchedBy(func(orgID int64) bool { return orgID == tmpOrgID }),
|
||||
mock.MatchedBy(func(cmd *sa.CreateServiceAccountForm) bool {
|
||||
return cmd.Name == extsvcPrefix+extSvcSlug && *cmd.Role == roletype.RoleNone
|
||||
return cmd.Name == ExtSvcPrefix+extSvcSlug && *cmd.Role == roletype.RoleNone
|
||||
}),
|
||||
)
|
||||
env.AcStore.AssertCalled(t, "SaveExternalServiceRole", mock.Anything,
|
||||
@@ -360,7 +360,7 @@ func TestExtSvcAccountsService_SaveExternalService(t *testing.T) {
|
||||
checks: func(t *testing.T, env *TestEnv) {
|
||||
env.SaSvc.AssertCalled(t, "RetrieveServiceAccountIdByName", mock.Anything,
|
||||
mock.MatchedBy(func(orgID int64) bool { return orgID == tmpOrgID }),
|
||||
mock.MatchedBy(func(slug string) bool { return slug == extsvcPrefix+extSvcSlug }))
|
||||
mock.MatchedBy(func(slug string) bool { return slug == ExtSvcPrefix+extSvcSlug }))
|
||||
env.AcStore.AssertCalled(t, "SaveExternalServiceRole", mock.Anything,
|
||||
mock.MatchedBy(func(cmd ac.SaveExternalServiceRoleCommand) bool {
|
||||
return cmd.ServiceAccountID == int64(11) && cmd.ExternalServiceID == extSvcSlug &&
|
||||
|
||||
Reference in New Issue
Block a user