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
This commit is contained in:
Karl Persson 2024-11-12 10:12:47 +01:00 committed by GitHub
parent 7eb4b974e0
commit 8d74296b6c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 156 additions and 125 deletions

View File

@ -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.

View File

@ -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 {

View File

@ -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.

View File

@ -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: {

View File

@ -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{}
}

View File

@ -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
}

View File

@ -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,

View File

@ -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,

View File

@ -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) {

View File

@ -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.