diff --git a/conf/defaults.ini b/conf/defaults.ini index 3e7fc20ef90..fc2f2eeaa19 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -615,6 +615,9 @@ id_response_header_prefix = X-Grafana # The header value will encode the namespace ("user:", "api-key:", "service-account:") id_response_header_namespaces = user api-key service-account +# Enables the use of managed service accounts for plugin authentication +managed_service_accounts_enabled = false + #################################### SSO Settings ########################### [sso_settings] # interval for reloading the SSO Settings from the database diff --git a/conf/sample.ini b/conf/sample.ini index a36d17ff618..236ad499da5 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -619,6 +619,9 @@ # The header value will encode the namespace ("user:", "api-key:", "service-account:") ;id_response_header_namespaces = user api-key service-account +# Enables the use of managed service accounts for plugin authentication +; managed_service_accounts_enabled = false + #################################### Anonymous Auth ###################### [auth.anonymous] # enable anonymous access diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index 256c50d5f6b..f10f1a48d2b 100644 --- a/pkg/api/plugins.go +++ b/pkg/api/plugins.go @@ -144,7 +144,7 @@ func (hs *HTTPServer) GetPluginList(c *contextmodel.ReqContext) response.Respons AngularDetected: pluginDef.Angular.Detected, } - if hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagExternalServiceAccounts) { + if hs.Cfg.ManagedServiceAccountsEnabled && hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagExternalServiceAccounts) { listItem.IAM = pluginDef.IAM } @@ -484,7 +484,7 @@ func (hs *HTTPServer) InstallPlugin(c *contextmodel.ReqContext) response.Respons return response.ErrOrFallback(http.StatusInternalServerError, "Failed to install plugin", err) } - if hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagExternalServiceAccounts) { + if hs.Cfg.ManagedServiceAccountsEnabled && hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagExternalServiceAccounts) { // This is a non-blocking function that verifies that the installer has // the permissions that the plugin requests to have on Grafana. // If we want to make this blocking, the check will have to happen before or during the installation. diff --git a/pkg/services/accesscontrol/acimpl/service.go b/pkg/services/accesscontrol/acimpl/service.go index 50616a33d3f..f9300fc1545 100644 --- a/pkg/services/accesscontrol/acimpl/service.go +++ b/pkg/services/accesscontrol/acimpl/service.go @@ -735,7 +735,7 @@ func (s *Service) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.SaveExternalServiceRole") defer span.End() - if !s.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) { + if !s.cfg.ManagedServiceAccountsEnabled || !s.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) { s.log.Debug("Registering an external service role is behind a feature flag, enable it to use this feature.") return nil } @@ -751,7 +751,7 @@ func (s *Service) DeleteExternalServiceRole(ctx context.Context, externalService ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.DeleteExternalServiceRole") defer span.End() - if !s.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) { + if !s.cfg.ManagedServiceAccountsEnabled || !s.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) { s.log.Debug("Deleting an external service role is behind a feature flag, enable it to use this feature.") return nil } diff --git a/pkg/services/accesscontrol/acimpl/service_test.go b/pkg/services/accesscontrol/acimpl/service_test.go index 7096b2fcac0..4c1dc562d8b 100644 --- a/pkg/services/accesscontrol/acimpl/service_test.go +++ b/pkg/services/accesscontrol/acimpl/service_test.go @@ -900,6 +900,7 @@ func TestService_SaveExternalServiceRole(t *testing.T) { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() ac := setupTestEnv(t) + ac.cfg.ManagedServiceAccountsEnabled = true ac.features = featuremgmt.WithFeatures(featuremgmt.FlagExternalServiceAccounts) for _, r := range tt.runs { err := ac.SaveExternalServiceRole(ctx, r.cmd) @@ -946,6 +947,7 @@ func TestService_DeleteExternalServiceRole(t *testing.T) { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() ac := setupTestEnv(t) + ac.cfg.ManagedServiceAccountsEnabled = true ac.features = featuremgmt.WithFeatures(featuremgmt.FlagExternalServiceAccounts) if tt.initCmd != nil { diff --git a/pkg/services/extsvcauth/registry/service.go b/pkg/services/extsvcauth/registry/service.go index cd6c603bcd8..9990ce15b87 100644 --- a/pkg/services/extsvcauth/registry/service.go +++ b/pkg/services/extsvcauth/registry/service.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/services/extsvcauth" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/serviceaccounts/extsvcaccounts" + "github.com/grafana/grafana/pkg/setting" ) var _ extsvcauth.ExternalServiceRegistry = &Registry{} @@ -28,9 +29,9 @@ type serverLocker interface { } type Registry struct { - features featuremgmt.FeatureToggles - logger log.Logger - saReg extsvcauth.ExternalServiceRegistry + enabled bool + logger log.Logger + saReg extsvcauth.ExternalServiceRegistry // FIXME (gamab): we can remove this field and use the saReg.GetExternalServiceNames directly extSvcProviders map[string]extsvcauth.AuthProvider @@ -38,10 +39,11 @@ type Registry struct { serverLock serverLocker } -func ProvideExtSvcRegistry(saSvc *extsvcaccounts.ExtSvcAccountsService, serverLock *serverlock.ServerLockService, features featuremgmt.FeatureToggles) *Registry { +func ProvideExtSvcRegistry(cfg *setting.Cfg, saSvc *extsvcaccounts.ExtSvcAccountsService, serverLock *serverlock.ServerLockService, features featuremgmt.FeatureToggles) *Registry { + enabled := features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAccounts) && cfg.ManagedServiceAccountsEnabled return &Registry{ extSvcProviders: map[string]extsvcauth.AuthProvider{}, - features: features, + enabled: enabled, lock: sync.Mutex{}, logger: log.New("extsvcauth.registry"), saReg: saSvc, @@ -110,8 +112,12 @@ func (r *Registry) RemoveExternalService(ctx context.Context, name string) error switch provider { case extsvcauth.ServiceAccounts: - if !r.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) { - r.logger.Debug("Skipping External Service removal, flag disabled", "service", name, "flag", featuremgmt.FlagExternalServiceAccounts) + if !r.enabled { + r.logger.Warn("Skipping External Service authentication, flag or configuration option is disabled", + "service", name, + "flag", featuremgmt.FlagExternalServiceAccounts, + "option", "ManagedServiceAccountsEnabled", + ) return nil } r.logger.Debug("Routing External Service removal to the External Service Account service", "service", name) @@ -140,8 +146,12 @@ func (r *Registry) SaveExternalService(ctx context.Context, cmd *extsvcauth.Exte switch cmd.AuthProvider { case extsvcauth.ServiceAccounts: - if !r.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) { - ctxLogger.Warn("Skipping External Service authentication, flag disabled", "service", cmd.Name, "flag", featuremgmt.FlagExternalServiceAccounts) + if !r.enabled { + ctxLogger.Warn("Skipping External Service authentication, flag or configuration option disabled", + "service", cmd.Name, + "flag", featuremgmt.FlagExternalServiceAccounts, + "option", "ManagedServiceAccountsEnabled", + ) return } ctxLogger.Debug("Routing the External Service registration to the External Service Account service", "service", cmd.Name) @@ -160,7 +170,7 @@ func (r *Registry) SaveExternalService(ctx context.Context, cmd *extsvcauth.Exte // retrieveExtSvcProviders fetches external services from store and map their associated provider func (r *Registry) retrieveExtSvcProviders(ctx context.Context) (map[string]extsvcauth.AuthProvider, error) { extsvcs := map[string]extsvcauth.AuthProvider{} - if r.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) { + if r.enabled { names, err := r.saReg.GetExternalServiceNames(ctx) if err != nil { return nil, err diff --git a/pkg/services/extsvcauth/registry/service_test.go b/pkg/services/extsvcauth/registry/service_test.go index 40c046f4ceb..30b5ef81138 100644 --- a/pkg/services/extsvcauth/registry/service_test.go +++ b/pkg/services/extsvcauth/registry/service_test.go @@ -8,7 +8,6 @@ import ( "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/services/extsvcauth" "github.com/grafana/grafana/pkg/services/extsvcauth/tests" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -30,7 +29,7 @@ func setupTestEnv(t *testing.T) *TestEnv { env := TestEnv{} env.saReg = tests.NewExternalServiceRegistryMock(t) env.r = &Registry{ - features: featuremgmt.WithFeatures(featuremgmt.FlagExternalServiceAccounts), + enabled: true, logger: log.New("extsvcauth.registry.test"), saReg: env.saReg, extSvcProviders: map[string]extsvcauth.AuthProvider{}, diff --git a/pkg/services/pluginsintegration/serviceregistration/serviceregistration.go b/pkg/services/pluginsintegration/serviceregistration/serviceregistration.go index f80ca8213bc..90eb7e67a61 100644 --- a/pkg/services/pluginsintegration/serviceregistration/serviceregistration.go +++ b/pkg/services/pluginsintegration/serviceregistration/serviceregistration.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/services/extsvcauth" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" + "github.com/grafana/grafana/pkg/setting" ) type Service struct { @@ -20,9 +21,10 @@ type Service struct { settingsSvc pluginsettings.Service } -func ProvideService(features featuremgmt.FeatureToggles, reg extsvcauth.ExternalServiceRegistry, settingsSvc pluginsettings.Service) *Service { +func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, reg extsvcauth.ExternalServiceRegistry, settingsSvc pluginsettings.Service) *Service { + enabled := features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAccounts) && cfg.ManagedServiceAccountsEnabled s := &Service{ - featureEnabled: features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAccounts), + featureEnabled: enabled, log: log.New("plugins.external.registration"), reg: reg, settingsSvc: settingsSvc, diff --git a/pkg/services/serviceaccounts/api/api.go b/pkg/services/serviceaccounts/api/api.go index a0db940a2bc..322fc34d9ad 100644 --- a/pkg/services/serviceaccounts/api/api.go +++ b/pkg/services/serviceaccounts/api/api.go @@ -40,6 +40,7 @@ func NewServiceAccountsAPI( permissionService accesscontrol.ServiceAccountPermissionsService, features featuremgmt.FeatureToggles, ) *ServiceAccountsAPI { + enabled := features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAccounts) && cfg.ManagedServiceAccountsEnabled return &ServiceAccountsAPI{ cfg: cfg, service: service, @@ -48,7 +49,7 @@ func NewServiceAccountsAPI( RouterRegister: routerRegister, log: log.New("serviceaccounts.api"), permissionService: permissionService, - isExternalSAEnabled: features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAccounts), + isExternalSAEnabled: enabled, } } diff --git a/pkg/services/serviceaccounts/extsvcaccounts/service.go b/pkg/services/serviceaccounts/extsvcaccounts/service.go index d4802d993cc..bd4b7ff9cf3 100644 --- a/pkg/services/serviceaccounts/extsvcaccounts/service.go +++ b/pkg/services/serviceaccounts/extsvcaccounts/service.go @@ -28,12 +28,12 @@ import ( type ExtSvcAccountsService struct { acSvc ac.Service defaultOrgID int64 - features featuremgmt.FeatureToggles logger log.Logger metrics *metrics saSvc sa.Service skvStore kvstore.SecretsKVStore tracer tracing.Tracer + enabled bool } func ProvideExtSvcAccountsService(acSvc ac.Service, cfg *setting.Cfg, bus bus.Bus, db db.DB, features featuremgmt.FeatureToggles, reg prometheus.Registerer, saSvc *manager.ServiceAccountsService, secretsSvc secrets.Service, tracer tracing.Tracer) *ExtSvcAccountsService { @@ -43,12 +43,12 @@ func ProvideExtSvcAccountsService(acSvc ac.Service, cfg *setting.Cfg, bus bus.Bu defaultOrgID: cfg.DefaultOrgID(), logger: logger, saSvc: saSvc, - features: features, skvStore: kvstore.NewSQLSecretsKVStore(db, secretsSvc, logger), // Using SQL store to avoid a cyclic dependency tracer: tracer, + enabled: cfg.ManagedServiceAccountsEnabled && features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAccounts), } - if features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAccounts) { + if esa.enabled { // Register the metrics esa.metrics = newMetrics(reg, esa.defaultOrgID, saSvc, logger) @@ -138,7 +138,7 @@ func (esa *ExtSvcAccountsService) GetExternalServiceNames(ctx context.Context) ( // SaveExternalService creates, updates or delete a service account (and its token) with the requested permissions. func (esa *ExtSvcAccountsService) SaveExternalService(ctx context.Context, cmd *extsvcauth.ExternalServiceRegistration) (*extsvcauth.ExternalService, error) { // This is double proofing, we should never reach here anyway the flags have already been checked. - if !esa.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) { + if !esa.enabled { esa.logger.FromContext(ctx).Warn("This feature is behind a feature flag, please set it if you want to save external services") return nil, nil } @@ -184,7 +184,7 @@ func (esa *ExtSvcAccountsService) SaveExternalService(ctx context.Context, cmd * func (esa *ExtSvcAccountsService) RemoveExternalService(ctx context.Context, name string) error { // This is double proofing, we should never reach here anyway the flags have already been checked. - if !esa.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) { + if !esa.enabled { esa.logger.Warn("This feature is behind a feature flag, please set it if you want to save external services") return nil } @@ -225,7 +225,7 @@ func (esa *ExtSvcAccountsService) RemoveExtSvcAccount(ctx context.Context, orgID // ManageExtSvcAccount creates, updates or deletes the service account associated with an external service func (esa *ExtSvcAccountsService) ManageExtSvcAccount(ctx context.Context, cmd *sa.ManageExtSvcAccountCmd) (int64, error) { // This is double proofing, we should never reach here anyway the flags have already been checked. - if !esa.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) { + if !esa.enabled { esa.logger.FromContext(ctx).Warn("This feature is behind a feature flag, please set it if you want to save external services") return 0, nil } diff --git a/pkg/services/serviceaccounts/extsvcaccounts/service_test.go b/pkg/services/serviceaccounts/extsvcaccounts/service_test.go index dbb1fa67c4e..c687b717ed9 100644 --- a/pkg/services/serviceaccounts/extsvcaccounts/service_test.go +++ b/pkg/services/serviceaccounts/extsvcaccounts/service_test.go @@ -41,6 +41,7 @@ func setupTestEnv(t *testing.T) *TestEnv { t.Helper() cfg := setting.NewCfg() + cfg.ManagedServiceAccountsEnabled = true fmgt := featuremgmt.WithFeatures(featuremgmt.FlagExternalServiceAccounts) env := &TestEnv{ @@ -50,13 +51,13 @@ func setupTestEnv(t *testing.T) *TestEnv { } logger := log.New("extsvcaccounts.test") env.S = &ExtSvcAccountsService{ + enabled: true, acSvc: acimpl.ProvideOSSService( cfg, env.AcStore, &resourcepermissions.FakeActionSetSvc{}, localcache.New(0, 0), fmgt, tracing.InitializeTracerForTest(), nil, nil, permreg.ProvidePermissionRegistry(), ), defaultOrgID: autoAssignOrgID, - features: fmgt, logger: logger, metrics: newMetrics(nil, autoAssignOrgID, env.SaSvc, logger), saSvc: env.SaSvc, diff --git a/pkg/services/serviceaccounts/proxy/service.go b/pkg/services/serviceaccounts/proxy/service.go index 3cb5f404721..8da7af34a41 100644 --- a/pkg/services/serviceaccounts/proxy/service.go +++ b/pkg/services/serviceaccounts/proxy/service.go @@ -38,7 +38,7 @@ func ProvideServiceAccountsProxy( s := &ServiceAccountsProxy{ log: log.New("serviceaccounts.proxy"), proxiedService: proxiedService, - isProxyEnabled: features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAccounts), + isProxyEnabled: cfg.ManagedServiceAccountsEnabled && features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAccounts), } serviceaccountsAPI := api.NewServiceAccountsAPI(cfg, s, ac, accesscontrolService, routeRegister, permissionService, features) diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 2488ea5fddd..306ccff0392 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -245,6 +245,7 @@ type Cfg struct { IDResponseHeaderEnabled bool IDResponseHeaderPrefix string IDResponseHeaderNamespaces map[string]struct{} + ManagedServiceAccountsEnabled bool // AWS Plugin Auth AWSAllowedAuthProviders []string @@ -1668,6 +1669,10 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) { for _, provider := range util.SplitString(providers) { cfg.SSOSettingsConfigurableProviders[provider] = true } + + // Managed Service Accounts + cfg.ManagedServiceAccountsEnabled = auth.Key("managed_service_accounts_enabled").MustBool(false) + return nil }