grafana/pkg/services/serviceaccounts/manager/service.go

268 lines
8.7 KiB
Go

package manager
import (
"context"
"errors"
"fmt"
"time"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/kvstore"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/serviceaccounts/api"
"github.com/grafana/grafana/pkg/services/serviceaccounts/database"
"github.com/grafana/grafana/pkg/services/serviceaccounts/secretscan"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
)
const (
metricsCollectionInterval = time.Minute * 30
defaultSecretScanInterval = time.Minute * 5
)
type ServiceAccountsService struct {
store store
log log.Logger
backgroundLog log.Logger
secretScanService secretscan.Checker
secretScanEnabled bool
secretScanInterval time.Duration
}
func ProvideServiceAccountsService(
cfg *setting.Cfg,
ac accesscontrol.AccessControl,
routeRegister routing.RouteRegister,
usageStats usagestats.Service,
store *sqlstore.SQLStore,
apiKeyService apikey.Service,
kvStore kvstore.KVStore,
userService user.Service,
orgService org.Service,
permissionService accesscontrol.ServiceAccountPermissionsService,
accesscontrolService accesscontrol.Service,
) (*ServiceAccountsService, error) {
serviceAccountsStore := database.ProvideServiceAccountsStore(
cfg,
store,
apiKeyService,
kvStore,
userService,
orgService,
)
s := &ServiceAccountsService{
store: serviceAccountsStore,
log: log.New("serviceaccounts"),
backgroundLog: log.New("serviceaccounts.background"),
}
if err := RegisterRoles(accesscontrolService); err != nil {
s.log.Error("Failed to register roles", "error", err)
}
usageStats.RegisterMetricsFunc(s.getUsageMetrics)
serviceaccountsAPI := api.NewServiceAccountsAPI(cfg, s, ac, accesscontrolService, routeRegister, permissionService)
serviceaccountsAPI.RegisterAPIEndpoints()
s.secretScanEnabled = cfg.SectionWithEnvOverrides("secretscan").Key("enabled").MustBool(false)
s.secretScanInterval = cfg.SectionWithEnvOverrides("secretscan").
Key("interval").MustDuration(defaultSecretScanInterval)
if s.secretScanEnabled {
var errSecret error
s.secretScanService, errSecret = secretscan.NewService(s.store, cfg)
if errSecret != nil {
s.secretScanEnabled = false
s.log.Warn("Failed to initialize secret scan service. secret scan is disabled",
"error", errSecret.Error())
}
}
return s, nil
}
func (sa *ServiceAccountsService) Run(ctx context.Context) error {
sa.backgroundLog.Debug("Service initialized")
if _, err := sa.getUsageMetrics(ctx); err != nil {
sa.log.Warn("Failed to get usage metrics", "error", err.Error())
}
updateStatsTicker := time.NewTicker(metricsCollectionInterval)
defer updateStatsTicker.Stop()
// Enforce a minimum interval of 1 minute.
if sa.secretScanEnabled && sa.secretScanInterval < time.Minute {
sa.backgroundLog.Warn("Secret scan interval is too low, increasing to " +
defaultSecretScanInterval.String())
sa.secretScanInterval = defaultSecretScanInterval
}
tokenCheckTicker := time.NewTicker(sa.secretScanInterval)
if !sa.secretScanEnabled {
tokenCheckTicker.Stop()
} else {
sa.backgroundLog.Debug("Enabled token secret check and executing first check")
if err := sa.secretScanService.CheckTokens(ctx); err != nil {
sa.backgroundLog.Warn("Failed to check for leaked tokens", "error", err.Error())
}
defer tokenCheckTicker.Stop()
}
for {
select {
case <-ctx.Done():
if err := ctx.Err(); err != nil {
return fmt.Errorf("context error in service account background service: %w", ctx.Err())
}
sa.backgroundLog.Debug("Stopped service account background service")
return nil
case <-updateStatsTicker.C:
sa.backgroundLog.Debug("Updating usage metrics")
if _, err := sa.getUsageMetrics(ctx); err != nil {
sa.backgroundLog.Warn("Failed to get usage metrics", "error", err.Error())
}
case <-tokenCheckTicker.C:
sa.backgroundLog.Debug("Checking for leaked tokens")
if err := sa.secretScanService.CheckTokens(ctx); err != nil {
sa.backgroundLog.Warn("Failed to check for leaked tokens", "error", err.Error())
}
}
}
}
func (sa *ServiceAccountsService) CreateServiceAccount(ctx context.Context, orgID int64, saForm *serviceaccounts.CreateServiceAccountForm) (*serviceaccounts.ServiceAccountDTO, error) {
if err := validOrgID(orgID); err != nil {
return nil, err
}
return sa.store.CreateServiceAccount(ctx, orgID, saForm)
}
func (sa *ServiceAccountsService) RetrieveServiceAccount(ctx context.Context, orgID int64, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error) {
if err := validOrgID(orgID); err != nil {
return nil, err
}
if err := validServiceAccountID(serviceAccountID); err != nil {
return nil, err
}
return sa.store.RetrieveServiceAccount(ctx, orgID, serviceAccountID)
}
func (sa *ServiceAccountsService) RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error) {
if err := validOrgID(orgID); err != nil {
return 0, err
}
if name == "" {
return 0, errors.New("name is required")
}
return sa.store.RetrieveServiceAccountIdByName(ctx, orgID, name)
}
func (sa *ServiceAccountsService) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error {
if err := validOrgID(orgID); err != nil {
return err
}
if err := validServiceAccountID(serviceAccountID); err != nil {
return err
}
return sa.store.DeleteServiceAccount(ctx, orgID, serviceAccountID)
}
func (sa *ServiceAccountsService) UpdateServiceAccount(ctx context.Context, orgID int64, serviceAccountID int64, saForm *serviceaccounts.UpdateServiceAccountForm) (*serviceaccounts.ServiceAccountProfileDTO, error) {
if err := validOrgID(orgID); err != nil {
return nil, err
}
if err := validServiceAccountID(serviceAccountID); err != nil {
return nil, err
}
return sa.store.UpdateServiceAccount(ctx, orgID, serviceAccountID, saForm)
}
func (sa *ServiceAccountsService) SearchOrgServiceAccounts(ctx context.Context, query *serviceaccounts.SearchOrgServiceAccountsQuery) (*serviceaccounts.SearchOrgServiceAccountsResult, error) {
if query.Page <= 0 || query.Limit <= 0 {
query.SetDefaults()
// optional: logging
}
return sa.store.SearchOrgServiceAccounts(ctx, query)
}
func (sa *ServiceAccountsService) ListTokens(ctx context.Context, query *serviceaccounts.GetSATokensQuery) ([]apikey.APIKey, error) {
return sa.store.ListTokens(ctx, query)
}
func (sa *ServiceAccountsService) AddServiceAccountToken(ctx context.Context, serviceAccountID int64, query *serviceaccounts.AddServiceAccountTokenCommand) (*apikey.APIKey, error) {
if err := validServiceAccountID(serviceAccountID); err != nil {
return nil, err
}
return sa.store.AddServiceAccountToken(ctx, serviceAccountID, query)
}
func (sa *ServiceAccountsService) DeleteServiceAccountToken(ctx context.Context, orgID, serviceAccountID int64, tokenID int64) error {
if err := validOrgID(orgID); err != nil {
return err
}
if err := validServiceAccountID(serviceAccountID); err != nil {
return err
}
if err := validServiceAccountTokenID(tokenID); err != nil {
return err
}
return sa.store.DeleteServiceAccountToken(ctx, orgID, serviceAccountID, tokenID)
}
func (sa *ServiceAccountsService) MigrateApiKey(ctx context.Context, orgID, keyID int64) error {
if err := validOrgID(orgID); err != nil {
return err
}
if err := validAPIKeyID(keyID); err != nil {
return err
}
return sa.store.MigrateApiKey(ctx, orgID, keyID)
}
func (sa *ServiceAccountsService) MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) (*serviceaccounts.MigrationResult, error) {
if err := validOrgID(orgID); err != nil {
return nil, err
}
return sa.store.MigrateApiKeysToServiceAccounts(ctx, orgID)
}
func validOrgID(orgID int64) error {
if orgID == 0 {
return serviceaccounts.ErrServiceAccountInvalidOrgID.Errorf("invalid org ID 0 has been specified")
}
return nil
}
func validServiceAccountID(serviceaccountID int64) error {
if serviceaccountID == 0 {
return serviceaccounts.ErrServiceAccountInvalidID.Errorf("invalid service account ID 0 has been specified")
}
return nil
}
func validServiceAccountTokenID(tokenID int64) error {
if tokenID == 0 {
return serviceaccounts.ErrServiceAccountInvalidTokenID.Errorf("invalid service account token ID 0 has been specified")
}
return nil
}
func validAPIKeyID(apiKeyID int64) error {
if apiKeyID == 0 {
return serviceaccounts.ErrServiceAccountInvalidAPIKeyID.Errorf("invalid API key ID 0 has been specified")
}
return nil
}