The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/serviceaccounts/manager/service.go

267 lines
8.7 KiB

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
}