Authz: Setup access claims for service identity (#100986)

* Setup access claims for service identity and add them to identityes without any claims
pull/101058/head
Karl Persson 4 months ago committed by GitHub
parent be81314e21
commit 16fda6f686
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 67
      pkg/apimachinery/identity/context.go
  2. 15
      pkg/apimachinery/identity/static.go
  3. 2
      pkg/services/authn/authnimpl/registration.go
  4. 32
      pkg/services/authn/authnimpl/sync/access_claims.go
  5. 5
      pkg/services/authn/grpcutils/inproc_exchanger.go

@ -5,6 +5,7 @@ import (
"fmt"
"reflect"
"github.com/grafana/authlib/authn"
"github.com/grafana/authlib/types"
)
@ -30,27 +31,34 @@ func checkNilRequester(r Requester) bool {
return r == nil || (reflect.ValueOf(r).Kind() == reflect.Ptr && reflect.ValueOf(r).IsNil())
}
const serviceName = "service"
const serviceNameForProvisioning = "provisioning"
const (
serviceName = "service"
serviceNameForProvisioning = "provisioning"
)
// WithServiceIdentity sets an identity representing the service itself in provided org and store it in context.
// This is useful for background tasks that has to communicate with unfied storage. It also returns a Requester with
// static permissions so it can be used in legacy code paths.
func WithServiceIdentity(ctx context.Context, orgID int64) (context.Context, Requester) {
r := &StaticRequester{
func newInternalIdentity(name string, namespace string, orgID int64) Requester {
return &StaticRequester{
Type: types.TypeAccessPolicy,
Name: serviceName,
UserUID: serviceName,
AuthID: serviceName,
Login: serviceName,
Name: name,
UserUID: name,
AuthID: name,
Login: name,
OrgRole: RoleAdmin,
Namespace: namespace,
IsGrafanaAdmin: true,
OrgID: orgID,
Permissions: map[int64]map[string][]string{
orgID: serviceIdentityPermissions,
},
AccessTokenClaims: ServiceIdentityClaims,
}
}
// WithServiceIdentity sets an identity representing the service itself in provided org and store it in context.
// This is useful for background tasks that has to communicate with unfied storage. It also returns a Requester with
// static permissions so it can be used in legacy code paths.
func WithServiceIdentity(ctx context.Context, orgID int64) (context.Context, Requester) {
r := newInternalIdentity(serviceName, "", orgID)
return WithRequester(ctx, r), r
}
@ -60,21 +68,7 @@ func WithProvisioningIdentitiy(ctx context.Context, namespace string) (context.C
return nil, nil, err
}
r := &StaticRequester{
Type: types.TypeAccessPolicy,
Name: serviceNameForProvisioning,
UserUID: serviceNameForProvisioning,
AuthID: serviceNameForProvisioning,
Login: serviceNameForProvisioning,
OrgRole: RoleAdmin,
IsGrafanaAdmin: true,
Namespace: namespace,
OrgID: ns.OrgID,
Permissions: map[int64]map[string][]string{
ns.OrgID: serviceIdentityPermissions,
},
}
r := newInternalIdentity(serviceNameForProvisioning, ns.Value, ns.OrgID)
return WithRequester(ctx, r), r, nil
}
@ -97,6 +91,14 @@ func getWildcardPermissions(actions ...string) map[string][]string {
return permissions
}
func getTokenPermissions(groups ...string) []string {
out := make([]string, 0, len(groups))
for _, group := range groups {
out = append(out, group+":*")
}
return out
}
// serviceIdentityPermissions is a list of wildcard permissions for provided actions.
// We should add every action required "internally" here.
var serviceIdentityPermissions = getWildcardPermissions(
@ -118,6 +120,19 @@ var serviceIdentityPermissions = getWildcardPermissions(
"teams:read", // accesscontrol.ActionTeamsRead,
)
var serviceIdentityTokenPermissions = getTokenPermissions(
"folder.grafana.app",
"dashboard.grafana.app",
"secret.grafana.app",
)
var ServiceIdentityClaims = &authn.Claims[authn.AccessTokenClaims]{
Rest: authn.AccessTokenClaims{
Permissions: serviceIdentityTokenPermissions,
DelegatedPermissions: serviceIdentityTokenPermissions,
},
}
func IsServiceIdentity(ctx context.Context) bool {
ident, ok := types.AuthInfoFrom(ctx)
if !ok {

@ -30,10 +30,11 @@ type StaticRequester struct {
Namespace string
IsGrafanaAdmin bool
// Permissions grouped by orgID and actions
Permissions map[int64]map[string][]string
IDToken string
IDTokenClaims *authnlib.Claims[authnlib.IDTokenClaims]
CacheKey string
Permissions map[int64]map[string][]string
IDToken string
IDTokenClaims *authnlib.Claims[authnlib.IDTokenClaims]
AccessTokenClaims *authnlib.Claims[authnlib.AccessTokenClaims]
CacheKey string
}
// GetID returns typed id for the entity
@ -62,10 +63,16 @@ func (u *StaticRequester) GetAudience() []string {
}
func (u *StaticRequester) GetTokenPermissions() []string {
if u.AccessTokenClaims != nil {
return u.AccessTokenClaims.Rest.Permissions
}
return []string{}
}
func (u *StaticRequester) GetTokenDelegatedPermissions() []string {
if u.AccessTokenClaims != nil {
return u.AccessTokenClaims.Rest.DelegatedPermissions
}
return []string{}
}

@ -145,5 +145,7 @@ func ProvideRegistration(
nsSync := sync.ProvideNamespaceSync(cfg)
authnSvc.RegisterPostAuthHook(nsSync.SyncNamespace, 150)
authnSvc.RegisterPostAuthHook(sync.AccessClaimsHook, 160)
return Registration{}
}

@ -0,0 +1,32 @@
package sync
import (
"context"
authnlib "github.com/grafana/authlib/authn"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
)
func NewAccessClaimsSync() AccessClaimsSync {
return AccessClaimsSync{}
}
type AccessClaimsSync struct{}
func AccessClaimsHook(ctx context.Context, id *authn.Identity, _ *authn.Request) error {
if id.AccessTokenClaims == nil {
// When normal authencation flows are used withint grafana we don't have any access token e.g. using user
// session. This makes it impossible to authorize using AccessClient because we don't have any access claims
// with deletegated permissions. To get around this we use the hardcoded delegated
// permissions.
id.AccessTokenClaims = &authnlib.Claims[authnlib.AccessTokenClaims]{
Rest: authnlib.AccessTokenClaims{
DelegatedPermissions: identity.ServiceIdentityClaims.Rest.DelegatedPermissions,
},
}
}
return nil
}

@ -9,6 +9,7 @@ import (
"github.com/go-jose/go-jose/v3/jwt"
"github.com/grafana/authlib/authn"
"github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/identity"
)
type inProcExchanger struct {
@ -37,8 +38,8 @@ func createInProcToken() (*authn.TokenExchangeResponse, error) {
},
Rest: authn.AccessTokenClaims{
Namespace: "*",
Permissions: []string{"folder.grafana.app:*", "dashboard.grafana.app:*"},
DelegatedPermissions: []string{"folder.grafana.app:*", "dashboard.grafana.app:*"},
Permissions: identity.ServiceIdentityClaims.Rest.Permissions,
DelegatedPermissions: identity.ServiceIdentityClaims.Rest.DelegatedPermissions,
},
}

Loading…
Cancel
Save