Files
grafana/pkg/registry/apis/provisioning/auth/identity.go
T

186 lines
5.4 KiB
Go

package auth
import (
"errors"
"fmt"
"log/slog"
"sync"
"time"
"golang.org/x/net/context"
"k8s.io/client-go/rest"
"github.com/grafana/authlib/claims"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/user"
)
type BackgroundIdentityService interface {
WorkerIdentity(ctx context.Context, namespace string) (identity.Requester, error)
// This will return a rest.Config with a worker identity attached in the context
RestConfigForNamespace(ctx context.Context, namespace string) (*rest.Config, error)
}
func ProvideProvisioningIdentityService(
serviceAccounts serviceaccounts.Service,
authn authn.Service,
clientConfigProvider apiserver.DirectRestConfigProvider,
// HACK, for now we will use a global grafana admin user
// service accounts need permissions granted (as far as i can tell)
users user.Service,
orgs org.Service,
) BackgroundIdentityService {
prefix := "provisioning-background-worker"
return &backgroundIdentities{
serviceAccountNamePrefix: prefix,
role: org.RoleAdmin,
clientConfigProvider: clientConfigProvider,
log: slog.Default().With("logger", "background-identities", "prefix", prefix),
accounts: make(map[int64]string),
serviceAccounts: serviceAccounts,
authn: authn,
users: users,
orgs: orgs,
}
}
type backgroundIdentities struct {
log *slog.Logger
serviceAccountNamePrefix string
role org.RoleType
clientConfigProvider apiserver.DirectRestConfigProvider
// typed ids
accounts map[int64]string
mutex sync.Mutex
users user.Service
serviceAccounts serviceaccounts.Service
authn authn.Service
orgs org.Service
}
func (o *backgroundIdentities) WorkerIdentity(ctx context.Context, namespace string) (identity.Requester, error) {
info, err := claims.ParseNamespace(namespace)
if err != nil {
return nil, err
}
o.mutex.Lock()
defer o.mutex.Unlock()
id, ok := o.accounts[info.OrgID]
if !ok {
// HACK, use the global admin!!!
id = "user:1"
// HACK -- (if false) and still allow lint
switch o.serviceAccountNamePrefix {
case "NOPE":
id, err = o.makeAdminUser(ctx, info.OrgID)
case "xxxx":
id, err = o.verifyServiceAccount(ctx, info.OrgID)
}
if err != nil {
return nil, err
}
o.accounts[info.OrgID] = id
}
return o.authn.ResolveIdentity(ctx, info.OrgID, id)
}
func (o *backgroundIdentities) makeAdminUser(ctx context.Context, orgId int64) (string, error) {
found, err := o.users.GetByLogin(ctx, &user.GetUserByLoginQuery{
LoginOrEmail: o.serviceAccountNamePrefix,
})
if found == nil {
// Same user across all orgs??
found, err = o.users.Create(ctx, &user.CreateUserCommand{
Login: o.serviceAccountNamePrefix,
Name: o.serviceAccountNamePrefix,
Email: o.serviceAccountNamePrefix,
OrgID: orgId,
IsAdmin: true,
DefaultOrgRole: string(org.RoleAdmin),
})
}
if err != nil {
return "", err
}
// Make sure the user exists in the org
err = o.orgs.UpdateOrgUser(ctx, &org.UpdateOrgUserCommand{
Role: identity.RoleAdmin,
OrgID: orgId,
UserID: found.ID,
})
return claims.NewTypeID(claims.TypeUser, found.UID), err
}
func (o *backgroundIdentities) verifyServiceAccount(ctx context.Context, orgId int64) (string, error) {
serviceAccountName := fmt.Sprintf("%s-org-%d", o.serviceAccountNamePrefix, orgId)
saForm := serviceaccounts.CreateServiceAccountForm{
Name: serviceAccountName,
Role: &o.role,
}
serviceAccount, err := o.serviceAccounts.CreateServiceAccount(ctx, orgId, &saForm)
if serviceAccount == nil {
accountAlreadyExists := errors.Is(err, serviceaccounts.ErrServiceAccountAlreadyExists)
if accountAlreadyExists {
accountId, err := o.serviceAccounts.RetrieveServiceAccountIdByName(ctx, orgId, serviceAccountName)
if err != nil {
o.log.Error("Failed to retrieve service account", "err", err, "accountName", serviceAccountName)
return "", err
}
// update org_role to make sure everything works properly if someone has changed the role since SA's original creation
dto, err := o.serviceAccounts.UpdateServiceAccount(ctx, orgId, accountId, &serviceaccounts.UpdateServiceAccountForm{
Name: &serviceAccountName,
Role: &o.role,
})
if err != nil {
o.log.Error("Failed to update service account", "err", err, "accountName", serviceAccountName)
return "", err
}
serviceAccount = &serviceaccounts.ServiceAccountDTO{
Id: dto.Id,
UID: dto.UID,
}
}
}
if serviceAccount == nil {
o.log.Error("Failed to retrieve service account", "err", err, "accountName", serviceAccountName)
return "", err
}
return claims.NewTypeID(claims.TypeServiceAccount, fmt.Sprintf("%d", serviceAccount.Id)), nil
}
func (o *backgroundIdentities) RestConfigForNamespace(ctx context.Context, namespace string) (*rest.Config, error) {
ts := time.Now()
id, err := o.WorkerIdentity(ctx, namespace)
if err != nil {
return nil, err
}
return o.clientConfigProvider.GetRestConfigForBackgroundWorker(func() identity.Requester {
if time.Since(ts) > time.Minute { // Get a fresh user every min
ts = time.Now()
id, _ = o.WorkerIdentity(ctx, namespace)
}
return id
}), nil
}