From 8d74296b6c9a5f20b4ac3e0739ce171699d58a81 Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Tue, 12 Nov 2024 10:12:47 +0100 Subject: [PATCH] Authn: Always set namespace (#96230) * Rename from AllowedKubernetesNamespace to Namespace * Use a sync hook to always set namespace for Identity. * format * Don't set uid when authenticating as user --- pkg/apimachinery/identity/requester.go | 4 +- pkg/apimachinery/identity/static.go | 34 ++-- pkg/apimachinery/identity/wrapper.go | 2 +- pkg/apiserver/endpoints/filters/requester.go | 4 +- pkg/services/authn/authnimpl/registration.go | 2 + .../authn/authnimpl/sync/namespace.go | 28 ++++ pkg/services/authn/clients/ext_jwt.go | 38 ++--- pkg/services/authn/clients/ext_jwt_test.go | 152 +++++++++--------- pkg/services/authn/identity.go | 9 +- pkg/services/user/identity.go | 8 +- 10 files changed, 156 insertions(+), 125 deletions(-) create mode 100644 pkg/services/authn/authnimpl/sync/namespace.go diff --git a/pkg/apimachinery/identity/requester.go b/pkg/apimachinery/identity/requester.go index 5105a9dfb5f..84391470ab0 100644 --- a/pkg/apimachinery/identity/requester.go +++ b/pkg/apimachinery/identity/requester.go @@ -52,9 +52,9 @@ type Requester interface { GetOrgName() string // GetAuthID returns external id for entity. GetAuthID() string - // GetAllowedKubernetesNamespace returns either "*" or the single namespace this requester has access to + // GetNamespace returns either "*" or the single namespace this requester has access to // An empty value means the implementation has not specified a kubernetes namespace. - GetAllowedKubernetesNamespace() string + GetNamespace() string // GetAuthenticatedBy returns the authentication method used to authenticate the entity. GetAuthenticatedBy() string // IsAuthenticatedBy returns true if entity was authenticated by any of supplied providers. diff --git a/pkg/apimachinery/identity/static.go b/pkg/apimachinery/identity/static.go index b3ed56491f4..6f84fd65c50 100644 --- a/pkg/apimachinery/identity/static.go +++ b/pkg/apimachinery/identity/static.go @@ -14,21 +14,21 @@ var _ Requester = &StaticRequester{} // This is mostly copied from: // https://github.com/grafana/grafana/blob/v11.0.0/pkg/services/user/identity.go#L16 type StaticRequester struct { - Type claims.IdentityType - UserID int64 - UserUID string - OrgID int64 - OrgName string - OrgRole RoleType - Login string - Name string - DisplayName string - Email string - EmailVerified bool - AuthID string - AuthenticatedBy string - AllowedKubernetesNamespace string - IsGrafanaAdmin bool + Type claims.IdentityType + UserID int64 + UserUID string + OrgID int64 + OrgName string + OrgRole RoleType + Login string + Name string + DisplayName string + Email string + EmailVerified bool + AuthID string + AuthenticatedBy string + Namespace string + IsGrafanaAdmin bool // Permissions grouped by orgID and actions Permissions map[int64]map[string][]string IDToken string @@ -175,8 +175,8 @@ func (u *StaticRequester) GetAuthID() string { return u.AuthID } -func (u *StaticRequester) GetAllowedKubernetesNamespace() string { - return u.AllowedKubernetesNamespace +func (u *StaticRequester) GetNamespace() string { + return u.Namespace } func (u *StaticRequester) GetAuthenticatedBy() string { diff --git a/pkg/apimachinery/identity/wrapper.go b/pkg/apimachinery/identity/wrapper.go index 76823167705..02f8d591f58 100644 --- a/pkg/apimachinery/identity/wrapper.go +++ b/pkg/apimachinery/identity/wrapper.go @@ -85,7 +85,7 @@ func (i *IDClaimsWrapper) JTI() string { // GetNamespace implements claims.AccessClaims. func (i *IDClaimsWrapper) Namespace() string { - return i.Source.GetAllowedKubernetesNamespace() + return i.Source.GetNamespace() } // GetNotBefore implements claims.AccessClaims. diff --git a/pkg/apiserver/endpoints/filters/requester.go b/pkg/apiserver/endpoints/filters/requester.go index acb2127abd3..d01b33929d2 100644 --- a/pkg/apiserver/endpoints/filters/requester.go +++ b/pkg/apiserver/endpoints/filters/requester.go @@ -45,8 +45,8 @@ func WithRequester(handler http.Handler) http.Handler { Login: info.GetName(), OrgRole: identity.RoleAdmin, - IsGrafanaAdmin: true, - AllowedKubernetesNamespace: "default", + IsGrafanaAdmin: true, + Namespace: "default", Permissions: map[int64]map[string][]string{ orgId: { diff --git a/pkg/services/authn/authnimpl/registration.go b/pkg/services/authn/authnimpl/registration.go index 85358a3545a..562f638565c 100644 --- a/pkg/services/authn/authnimpl/registration.go +++ b/pkg/services/authn/authnimpl/registration.go @@ -119,5 +119,7 @@ func ProvideRegistration( authnSvc.RegisterPostAuthHook(rbacSync.SyncPermissionsHook, 120) authnSvc.RegisterPostLoginHook(orgSync.SetDefaultOrgHook, 140) + nsSync := sync.ProvideNamespaceSync(cfg) + authnSvc.RegisterPostAuthHook(nsSync.SyncNamespace, 150) return Registration{} } diff --git a/pkg/services/authn/authnimpl/sync/namespace.go b/pkg/services/authn/authnimpl/sync/namespace.go new file mode 100644 index 00000000000..70647719882 --- /dev/null +++ b/pkg/services/authn/authnimpl/sync/namespace.go @@ -0,0 +1,28 @@ +package sync + +import ( + "context" + + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" + "github.com/grafana/grafana/pkg/services/authn" + "github.com/grafana/grafana/pkg/setting" +) + +func ProvideNamespaceSync(cfg *setting.Cfg) *NamespaceSync { + return &NamespaceSync{ + mapper: request.GetNamespaceMapper(cfg), + } +} + +type NamespaceSync struct { + mapper request.NamespaceMapper +} + +func (s *NamespaceSync) SyncNamespace(ctx context.Context, id *authn.Identity, _ *authn.Request) error { + if id.Namespace != "" { + return nil + } + + id.Namespace = s.mapper(id.OrgID) + return nil +} diff --git a/pkg/services/authn/clients/ext_jwt.go b/pkg/services/authn/clients/ext_jwt.go index 8c9c33b0e4e..1d352f3cbfe 100644 --- a/pkg/services/authn/clients/ext_jwt.go +++ b/pkg/services/authn/clients/ext_jwt.go @@ -129,21 +129,21 @@ func (s *ExtendedJWT) authenticateAsUser( } // For use in service layer, allow higher privilege - allowedKubernetesNamespace := accessTokenClaims.Rest.Namespace + namespace := accessTokenClaims.Rest.Namespace if len(s.cfg.StackID) > 0 { // For single-tenant cloud use, choose the lower of the two (id token will always have the specific namespace) - allowedKubernetesNamespace = idTokenClaims.Rest.Namespace + namespace = idTokenClaims.Rest.Namespace } return &authn.Identity{ - ID: id, - Type: t, - OrgID: s.cfg.DefaultOrgID(), - AccessTokenClaims: &accessTokenClaims, - IDTokenClaims: &idTokenClaims, - AuthenticatedBy: login.ExtendedJWTModule, - AuthID: accessTokenClaims.Subject, - AllowedKubernetesNamespace: allowedKubernetesNamespace, + ID: id, + Type: t, + OrgID: s.cfg.DefaultOrgID(), + AccessTokenClaims: &accessTokenClaims, + IDTokenClaims: &idTokenClaims, + AuthenticatedBy: login.ExtendedJWTModule, + AuthID: accessTokenClaims.Subject, + Namespace: namespace, ClientParams: authn.ClientParams{ SyncPermissions: true, FetchPermissionsParams: authn.FetchPermissionsParams{ @@ -184,15 +184,15 @@ func (s *ExtendedJWT) authenticateAsService(accessTokenClaims authlib.Claims[aut } return &authn.Identity{ - ID: id, - UID: id, - Name: id, - Type: t, - OrgID: s.cfg.DefaultOrgID(), - AccessTokenClaims: &accessTokenClaims, - AuthenticatedBy: login.ExtendedJWTModule, - AuthID: accessTokenClaims.Subject, - AllowedKubernetesNamespace: accessTokenClaims.Rest.Namespace, + ID: id, + UID: id, + Name: id, + Type: t, + OrgID: s.cfg.DefaultOrgID(), + AccessTokenClaims: &accessTokenClaims, + AuthenticatedBy: login.ExtendedJWTModule, + AuthID: accessTokenClaims.Subject, + Namespace: accessTokenClaims.Rest.Namespace, ClientParams: authn.ClientParams{ SyncPermissions: true, FetchPermissionsParams: fetchPermissionsParams, diff --git a/pkg/services/authn/clients/ext_jwt_test.go b/pkg/services/authn/clients/ext_jwt_test.go index 3f9e9ee6197..10bd830d7f6 100644 --- a/pkg/services/authn/clients/ext_jwt_test.go +++ b/pkg/services/authn/clients/ext_jwt_test.go @@ -226,15 +226,15 @@ func TestExtendedJWT_Authenticate(t *testing.T) { accessToken: &validAccessTokenClaims, orgID: 1, want: &authn.Identity{ - ID: "this-uid", - UID: "this-uid", - Name: "this-uid", - Type: claims.TypeAccessPolicy, - OrgID: 1, - AccessTokenClaims: &validAccessTokenClaims, - AllowedKubernetesNamespace: "default", - AuthenticatedBy: "extendedjwt", - AuthID: "access-policy:this-uid", + ID: "this-uid", + UID: "this-uid", + Name: "this-uid", + Type: claims.TypeAccessPolicy, + OrgID: 1, + AccessTokenClaims: &validAccessTokenClaims, + Namespace: "default", + AuthenticatedBy: "extendedjwt", + AuthID: "access-policy:this-uid", ClientParams: authn.ClientParams{ SyncPermissions: true, FetchPermissionsParams: authn.FetchPermissionsParams{Roles: []string{"fixed:folders:reader"}, AllowedActions: []string{"folders:read"}}}, @@ -245,15 +245,15 @@ func TestExtendedJWT_Authenticate(t *testing.T) { accessToken: &validAccessTokenClaimsWildcard, orgID: 1, want: &authn.Identity{ - ID: "this-uid", - UID: "this-uid", - Name: "this-uid", - Type: claims.TypeAccessPolicy, - OrgID: 1, - AccessTokenClaims: &validAccessTokenClaimsWildcard, - AllowedKubernetesNamespace: "*", - AuthenticatedBy: "extendedjwt", - AuthID: "access-policy:this-uid", + ID: "this-uid", + UID: "this-uid", + Name: "this-uid", + Type: claims.TypeAccessPolicy, + OrgID: 1, + AccessTokenClaims: &validAccessTokenClaimsWildcard, + Namespace: "*", + AuthenticatedBy: "extendedjwt", + AuthID: "access-policy:this-uid", ClientParams: authn.ClientParams{ SyncPermissions: true, }, @@ -265,14 +265,14 @@ func TestExtendedJWT_Authenticate(t *testing.T) { idToken: &validIDTokenClaims, orgID: 1, want: &authn.Identity{ - ID: "2", - Type: claims.TypeUser, - OrgID: 1, - AccessTokenClaims: &validAccessTokenClaims, - IDTokenClaims: &validIDTokenClaims, - AllowedKubernetesNamespace: "default", - AuthenticatedBy: "extendedjwt", - AuthID: "access-policy:this-uid", + ID: "2", + Type: claims.TypeUser, + OrgID: 1, + AccessTokenClaims: &validAccessTokenClaims, + IDTokenClaims: &validIDTokenClaims, + Namespace: "default", + AuthenticatedBy: "extendedjwt", + AuthID: "access-policy:this-uid", ClientParams: authn.ClientParams{ FetchSyncedUser: true, SyncPermissions: true, @@ -288,14 +288,14 @@ func TestExtendedJWT_Authenticate(t *testing.T) { idToken: &validIDTokenClaims, orgID: 1, want: &authn.Identity{ - ID: "2", - Type: claims.TypeUser, - OrgID: 1, - AccessTokenClaims: &validAccessTokenClaimsWildcard, - IDTokenClaims: &validIDTokenClaims, - AllowedKubernetesNamespace: "*", - AuthenticatedBy: "extendedjwt", - AuthID: "access-policy:this-uid", + ID: "2", + Type: claims.TypeUser, + OrgID: 1, + AccessTokenClaims: &validAccessTokenClaimsWildcard, + IDTokenClaims: &validIDTokenClaims, + Namespace: "*", + AuthenticatedBy: "extendedjwt", + AuthID: "access-policy:this-uid", ClientParams: authn.ClientParams{ FetchSyncedUser: true, SyncPermissions: true, @@ -316,14 +316,14 @@ func TestExtendedJWT_Authenticate(t *testing.T) { }, }, want: &authn.Identity{ - ID: "2", - Type: claims.TypeUser, - OrgID: 1, - AccessTokenClaims: &validAccessTokenClaimsWildcard, - IDTokenClaims: &validIDTokenClaimsWithStackSet, - AllowedKubernetesNamespace: "stacks-1234", - AuthenticatedBy: "extendedjwt", - AuthID: "access-policy:this-uid", + ID: "2", + Type: claims.TypeUser, + OrgID: 1, + AccessTokenClaims: &validAccessTokenClaimsWildcard, + IDTokenClaims: &validIDTokenClaimsWithStackSet, + Namespace: "stacks-1234", + AuthenticatedBy: "extendedjwt", + AuthID: "access-policy:this-uid", ClientParams: authn.ClientParams{ FetchSyncedUser: true, SyncPermissions: true, @@ -343,15 +343,15 @@ func TestExtendedJWT_Authenticate(t *testing.T) { }, }, want: &authn.Identity{ - ID: "this-uid", - UID: "this-uid", - Name: "this-uid", - Type: claims.TypeAccessPolicy, - OrgID: 1, - AccessTokenClaims: &validAccessTokenClaimsWithStackSet, - AllowedKubernetesNamespace: "stacks-1234", - AuthenticatedBy: "extendedjwt", - AuthID: "access-policy:this-uid", + ID: "this-uid", + UID: "this-uid", + Name: "this-uid", + Type: claims.TypeAccessPolicy, + OrgID: 1, + AccessTokenClaims: &validAccessTokenClaimsWithStackSet, + Namespace: "stacks-1234", + AuthenticatedBy: "extendedjwt", + AuthID: "access-policy:this-uid", ClientParams: authn.ClientParams{ SyncPermissions: true, }, @@ -370,15 +370,15 @@ func TestExtendedJWT_Authenticate(t *testing.T) { }, }, want: &authn.Identity{ - ID: "this-uid", - UID: "this-uid", - Name: "this-uid", - Type: claims.TypeAccessPolicy, - OrgID: 1, - AccessTokenClaims: &validAccessTokenClaimsWithDeprecatedStackClaimSet, - AllowedKubernetesNamespace: "stack-1234", - AuthenticatedBy: "extendedjwt", - AuthID: "access-policy:this-uid", + ID: "this-uid", + UID: "this-uid", + Name: "this-uid", + Type: claims.TypeAccessPolicy, + OrgID: 1, + AccessTokenClaims: &validAccessTokenClaimsWithDeprecatedStackClaimSet, + Namespace: "stack-1234", + AuthenticatedBy: "extendedjwt", + AuthID: "access-policy:this-uid", ClientParams: authn.ClientParams{ SyncPermissions: true, }, @@ -398,14 +398,14 @@ func TestExtendedJWT_Authenticate(t *testing.T) { }, }, want: &authn.Identity{ - ID: "2", - Type: claims.TypeUser, - OrgID: 1, - AccessTokenClaims: &validAccessTokenClaimsWithDeprecatedStackClaimSet, - IDTokenClaims: &validIDTokenClaimsWithDeprecatedStackClaimSet, - AllowedKubernetesNamespace: "stack-1234", - AuthenticatedBy: "extendedjwt", - AuthID: "access-policy:this-uid", + ID: "2", + Type: claims.TypeUser, + OrgID: 1, + AccessTokenClaims: &validAccessTokenClaimsWithDeprecatedStackClaimSet, + IDTokenClaims: &validIDTokenClaimsWithDeprecatedStackClaimSet, + Namespace: "stack-1234", + AuthenticatedBy: "extendedjwt", + AuthID: "access-policy:this-uid", ClientParams: authn.ClientParams{ SyncPermissions: true, FetchSyncedUser: true, @@ -426,14 +426,14 @@ func TestExtendedJWT_Authenticate(t *testing.T) { }, }, want: &authn.Identity{ - ID: "2", - Type: claims.TypeUser, - OrgID: 1, - AccessTokenClaims: &validAccessTokenClaimsWildcard, - IDTokenClaims: &validIDTokenClaimsWithStackSet, - AllowedKubernetesNamespace: "stacks-1234", - AuthenticatedBy: "extendedjwt", - AuthID: "access-policy:this-uid", + ID: "2", + Type: claims.TypeUser, + OrgID: 1, + AccessTokenClaims: &validAccessTokenClaimsWildcard, + IDTokenClaims: &validIDTokenClaimsWithStackSet, + Namespace: "stacks-1234", + AuthenticatedBy: "extendedjwt", + AuthID: "access-policy:this-uid", ClientParams: authn.ClientParams{ FetchSyncedUser: true, SyncPermissions: true, diff --git a/pkg/services/authn/identity.go b/pkg/services/authn/identity.go index a072ee7d807..d6e6827321f 100644 --- a/pkg/services/authn/identity.go +++ b/pkg/services/authn/identity.go @@ -49,8 +49,8 @@ type Identity struct { // AuthId is the unique identifier for the entity in the external system. // Empty if the identity is provided by Grafana. AuthID string - // AllowedKubernetesNamespace - AllowedKubernetesNamespace string + // Namespace + Namespace string // IsDisabled is true if the entity is disabled. IsDisabled bool // HelpFlags1 is the help flags for the entity. @@ -189,8 +189,8 @@ func (i *Identity) GetLogin() string { return i.Login } -func (i *Identity) GetAllowedKubernetesNamespace() string { - return i.AllowedKubernetesNamespace +func (i *Identity) GetNamespace() string { + return i.Namespace } func (i *Identity) GetOrgID() int64 { @@ -287,6 +287,7 @@ func (i *Identity) SignedInUser() *user.SignedInUser { Permissions: i.Permissions, IDToken: i.IDToken, FallbackType: i.Type, + Namespace: i.Namespace, } if i.IsIdentityType(claims.TypeAPIKey) { diff --git a/pkg/services/user/identity.go b/pkg/services/user/identity.go index c6939b47b0d..f71c29f6b97 100644 --- a/pkg/services/user/identity.go +++ b/pkg/services/user/identity.go @@ -30,8 +30,8 @@ type SignedInUser struct { // AuthID will be set if user signed in using external method AuthID string // AuthenticatedBy be set if user signed in using external method - AuthenticatedBy string - AllowedKubernetesNamespace string + AuthenticatedBy string + Namespace string ApiKeyID int64 `xorm:"api_key_id"` IsServiceAccount bool `xorm:"is_service_account"` @@ -173,8 +173,8 @@ func (u *SignedInUser) HasUniqueId() bool { return u.IsRealUser() || u.IsApiKeyUser() || u.IsServiceAccountUser() } -func (u *SignedInUser) GetAllowedKubernetesNamespace() string { - return u.AllowedKubernetesNamespace +func (u *SignedInUser) GetNamespace() string { + return u.Namespace } // GetCacheKey returns a unique key for the entity.