mirror of
https://github.com/grafana/grafana.git
synced 2025-08-02 06:02:49 +08:00
Authz: Setup access claims for service identity (#100986)
* Setup access claims for service identity and add them to identityes without any claims
This commit is contained in:
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/grafana/authlib/authn"
|
||||||
"github.com/grafana/authlib/types"
|
"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())
|
return r == nil || (reflect.ValueOf(r).Kind() == reflect.Ptr && reflect.ValueOf(r).IsNil())
|
||||||
}
|
}
|
||||||
|
|
||||||
const serviceName = "service"
|
const (
|
||||||
const serviceNameForProvisioning = "provisioning"
|
serviceName = "service"
|
||||||
|
serviceNameForProvisioning = "provisioning"
|
||||||
|
)
|
||||||
|
|
||||||
// WithServiceIdentity sets an identity representing the service itself in provided org and store it in context.
|
func newInternalIdentity(name string, namespace string, orgID int64) Requester {
|
||||||
// This is useful for background tasks that has to communicate with unfied storage. It also returns a Requester with
|
return &StaticRequester{
|
||||||
// static permissions so it can be used in legacy code paths.
|
|
||||||
func WithServiceIdentity(ctx context.Context, orgID int64) (context.Context, Requester) {
|
|
||||||
r := &StaticRequester{
|
|
||||||
Type: types.TypeAccessPolicy,
|
Type: types.TypeAccessPolicy,
|
||||||
Name: serviceName,
|
Name: name,
|
||||||
UserUID: serviceName,
|
UserUID: name,
|
||||||
AuthID: serviceName,
|
AuthID: name,
|
||||||
Login: serviceName,
|
Login: name,
|
||||||
OrgRole: RoleAdmin,
|
OrgRole: RoleAdmin,
|
||||||
|
Namespace: namespace,
|
||||||
IsGrafanaAdmin: true,
|
IsGrafanaAdmin: true,
|
||||||
OrgID: orgID,
|
OrgID: orgID,
|
||||||
Permissions: map[int64]map[string][]string{
|
Permissions: map[int64]map[string][]string{
|
||||||
orgID: serviceIdentityPermissions,
|
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
|
return WithRequester(ctx, r), r
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,21 +68,7 @@ func WithProvisioningIdentitiy(ctx context.Context, namespace string) (context.C
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
r := &StaticRequester{
|
r := newInternalIdentity(serviceNameForProvisioning, ns.Value, ns.OrgID)
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return WithRequester(ctx, r), r, nil
|
return WithRequester(ctx, r), r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,6 +91,14 @@ func getWildcardPermissions(actions ...string) map[string][]string {
|
|||||||
return permissions
|
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.
|
// serviceIdentityPermissions is a list of wildcard permissions for provided actions.
|
||||||
// We should add every action required "internally" here.
|
// We should add every action required "internally" here.
|
||||||
var serviceIdentityPermissions = getWildcardPermissions(
|
var serviceIdentityPermissions = getWildcardPermissions(
|
||||||
@ -118,6 +120,19 @@ var serviceIdentityPermissions = getWildcardPermissions(
|
|||||||
"teams:read", // accesscontrol.ActionTeamsRead,
|
"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 {
|
func IsServiceIdentity(ctx context.Context) bool {
|
||||||
ident, ok := types.AuthInfoFrom(ctx)
|
ident, ok := types.AuthInfoFrom(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -30,10 +30,11 @@ type StaticRequester struct {
|
|||||||
Namespace string
|
Namespace string
|
||||||
IsGrafanaAdmin bool
|
IsGrafanaAdmin bool
|
||||||
// Permissions grouped by orgID and actions
|
// Permissions grouped by orgID and actions
|
||||||
Permissions map[int64]map[string][]string
|
Permissions map[int64]map[string][]string
|
||||||
IDToken string
|
IDToken string
|
||||||
IDTokenClaims *authnlib.Claims[authnlib.IDTokenClaims]
|
IDTokenClaims *authnlib.Claims[authnlib.IDTokenClaims]
|
||||||
CacheKey string
|
AccessTokenClaims *authnlib.Claims[authnlib.AccessTokenClaims]
|
||||||
|
CacheKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetID returns typed id for the entity
|
// GetID returns typed id for the entity
|
||||||
@ -62,10 +63,16 @@ func (u *StaticRequester) GetAudience() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *StaticRequester) GetTokenPermissions() []string {
|
func (u *StaticRequester) GetTokenPermissions() []string {
|
||||||
|
if u.AccessTokenClaims != nil {
|
||||||
|
return u.AccessTokenClaims.Rest.Permissions
|
||||||
|
}
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *StaticRequester) GetTokenDelegatedPermissions() []string {
|
func (u *StaticRequester) GetTokenDelegatedPermissions() []string {
|
||||||
|
if u.AccessTokenClaims != nil {
|
||||||
|
return u.AccessTokenClaims.Rest.DelegatedPermissions
|
||||||
|
}
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,5 +145,7 @@ func ProvideRegistration(
|
|||||||
|
|
||||||
nsSync := sync.ProvideNamespaceSync(cfg)
|
nsSync := sync.ProvideNamespaceSync(cfg)
|
||||||
authnSvc.RegisterPostAuthHook(nsSync.SyncNamespace, 150)
|
authnSvc.RegisterPostAuthHook(nsSync.SyncNamespace, 150)
|
||||||
|
authnSvc.RegisterPostAuthHook(sync.AccessClaimsHook, 160)
|
||||||
|
|
||||||
return Registration{}
|
return Registration{}
|
||||||
}
|
}
|
||||||
|
32
pkg/services/authn/authnimpl/sync/access_claims.go
Normal file
32
pkg/services/authn/authnimpl/sync/access_claims.go
Normal file
@ -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/go-jose/go-jose/v3/jwt"
|
||||||
"github.com/grafana/authlib/authn"
|
"github.com/grafana/authlib/authn"
|
||||||
"github.com/grafana/authlib/types"
|
"github.com/grafana/authlib/types"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
)
|
)
|
||||||
|
|
||||||
type inProcExchanger struct {
|
type inProcExchanger struct {
|
||||||
@ -37,8 +38,8 @@ func createInProcToken() (*authn.TokenExchangeResponse, error) {
|
|||||||
},
|
},
|
||||||
Rest: authn.AccessTokenClaims{
|
Rest: authn.AccessTokenClaims{
|
||||||
Namespace: "*",
|
Namespace: "*",
|
||||||
Permissions: []string{"folder.grafana.app:*", "dashboard.grafana.app:*"},
|
Permissions: identity.ServiceIdentityClaims.Rest.Permissions,
|
||||||
DelegatedPermissions: []string{"folder.grafana.app:*", "dashboard.grafana.app:*"},
|
DelegatedPermissions: identity.ServiceIdentityClaims.Rest.DelegatedPermissions,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user