K8s: namespace mapper should use authlib's util (#92332)

pull/92545/head
Charandas 9 months ago committed by GitHub
parent f9719d4ee9
commit af2e79aa83
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      go.mod
  2. 12
      go.sum
  3. 2
      go.work.sum
  4. 4
      pkg/apimachinery/go.mod
  5. 8
      pkg/apimachinery/go.sum
  6. 4
      pkg/apimachinery/identity/wrapper.go
  7. 2
      pkg/apiserver/go.mod
  8. 4
      pkg/apiserver/go.sum
  9. 9
      pkg/services/apiserver/endpoints/request/namespace.go
  10. 7
      pkg/services/apiserver/endpoints/request/namespace_test.go
  11. 62
      pkg/services/authn/clients/ext_jwt.go
  12. 145
      pkg/services/authn/clients/ext_jwt_test.go
  13. 5
      pkg/services/authz/client.go
  14. 4
      pkg/storage/unified/apistore/go.mod
  15. 8
      pkg/storage/unified/apistore/go.sum
  16. 4
      pkg/storage/unified/resource/go.mod
  17. 8
      pkg/storage/unified/resource/go.sum
  18. 4
      pkg/storage/unified/resource/grpc/authenticator.go

@ -74,9 +74,9 @@ require (
github.com/googleapis/gax-go/v2 v2.13.0 // @grafana/grafana-backend-group
github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group
github.com/gorilla/websocket v1.5.0 // @grafana/grafana-app-platform-squad
github.com/grafana/alerting v0.0.0-20240827075410-70248a7a3a67 // @grafana/alerting-backend
github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db // @grafana/identity-access-team
github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db // @grafana/identity-access-team
github.com/grafana/alerting v0.0.0-20240822131459-9daa6239cc41 // @grafana/alerting-backend
github.com/grafana/authlib v0.0.0-20240827201526-24af227df935 // @grafana/identity-access-team
github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd // @grafana/identity-access-team
github.com/grafana/codejen v0.0.3 // @grafana/dataviz-squad
github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code
github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics

@ -2246,12 +2246,12 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/alerting v0.0.0-20240827075410-70248a7a3a67 h1:3spByRvTR3Qo7uDCEVVLB7+5VYH1q4hxwqVLdNpcS6k=
github.com/grafana/alerting v0.0.0-20240827075410-70248a7a3a67/go.mod h1:GMLi6d09Xqo96fCVUjNk//rcjP5NKEdjOzfWIffD5r4=
github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db h1:z++X4DdoX+aNlZNT1ZY4cykiFay4+f077pa0AG48SGg=
github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db/go.mod h1:ptt910z9KFfpVSIbSbXvTRR7tS19mxD7EtmVbbJi/WE=
github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db h1:mDk0bwRV6rDrLSmKXftcPf9kLA9uH6EvxJvzpPW9bso=
github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
github.com/grafana/alerting v0.0.0-20240822131459-9daa6239cc41 h1:p+UsX43BoDH5YlG6xUd9xDS3M4sWouy8VJ+ODv5S6uE=
github.com/grafana/alerting v0.0.0-20240822131459-9daa6239cc41/go.mod h1:GMLi6d09Xqo96fCVUjNk//rcjP5NKEdjOzfWIffD5r4=
github.com/grafana/authlib v0.0.0-20240827201526-24af227df935 h1:nT4UY61s2flsiLkU2jDqtqFhOLwqh355+8ZhnavKoMQ=
github.com/grafana/authlib v0.0.0-20240827201526-24af227df935/go.mod h1:ER7bMzNNWTN/5Zl3pwqfgS6XEhcanjrvL7lOp8Ow6oc=
github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd h1:sIlR7n38/MnZvX2qxDEszywXdI5soCwQ78aTDSARvus=
github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw=
github.com/grafana/codejen v0.0.3/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s=
github.com/grafana/cue v0.0.0-20230926092038-971951014e3f h1:TmYAMnqg3d5KYEAaT6PtTguL2GjLfvr6wnAX8Azw6tQ=

@ -714,6 +714,8 @@ github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/grafana/authlib v0.0.0-20240730122259-a0d13672efb1/go.mod h1:YA9We4kTafu7mlMnUh3In6Q2wpg8fYN3ycgCKOK1TB8=
github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
github.com/grafana/authlib/claims v0.0.0-20240827173836-433b4fdb2f25/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
github.com/grafana/authlib/claims v0.0.0-20240827201239-81213c6670d3/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
github.com/grafana/gomemcache v0.0.0-20240229205252-cd6a66d6fb56/go.mod h1:PGk3RjYHpxMM8HFPhKKo+vve3DdlPUELZLSDEFehPuU=
github.com/grafana/grafana-plugin-sdk-go v0.235.0/go.mod h1:6n9LbrjGL3xAATntYVNcIi90G9BVHRJjzHKz5FXVfWw=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20240422145632-c33c6b5b6e6b h1:HCbWyVL6vi7gxyO76gQksSPH203oBJ1MJ3JcG1OQlsg=

@ -3,8 +3,8 @@ module github.com/grafana/grafana/pkg/apimachinery
go 1.23.0
require (
github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db // @grafana/identity-access-team
github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db // @grafana/identity-access-team
github.com/grafana/authlib v0.0.0-20240827201526-24af227df935 // @grafana/identity-access-team
github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd // @grafana/identity-access-team
github.com/stretchr/testify v1.9.0
k8s.io/apimachinery v0.31.0
k8s.io/apiserver v0.31.0

@ -28,10 +28,10 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db h1:z++X4DdoX+aNlZNT1ZY4cykiFay4+f077pa0AG48SGg=
github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db/go.mod h1:ptt910z9KFfpVSIbSbXvTRR7tS19mxD7EtmVbbJi/WE=
github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db h1:mDk0bwRV6rDrLSmKXftcPf9kLA9uH6EvxJvzpPW9bso=
github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
github.com/grafana/authlib v0.0.0-20240827201526-24af227df935 h1:nT4UY61s2flsiLkU2jDqtqFhOLwqh355+8ZhnavKoMQ=
github.com/grafana/authlib v0.0.0-20240827201526-24af227df935/go.mod h1:ER7bMzNNWTN/5Zl3pwqfgS6XEhcanjrvL7lOp8Ow6oc=
github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd h1:sIlR7n38/MnZvX2qxDEszywXdI5soCwQ78aTDSARvus=
github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=

@ -13,6 +13,10 @@ type IDClaimsWrapper struct {
Source Requester
}
func (i *IDClaimsWrapper) IsNil() bool {
return i.Source.IsNil()
}
// GetAuthenticatedBy implements claims.IdentityClaims.
func (i *IDClaimsWrapper) AuthenticatedBy() string {
return i.Source.GetAuthenticatedBy()

@ -4,7 +4,7 @@ go 1.23.0
require (
github.com/google/go-cmp v0.6.0
github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db
github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1
github.com/prometheus/client_golang v1.20.0
github.com/stretchr/testify v1.9.0

@ -77,8 +77,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db h1:mDk0bwRV6rDrLSmKXftcPf9kLA9uH6EvxJvzpPW9bso=
github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd h1:sIlR7n38/MnZvX2qxDEszywXdI5soCwQ78aTDSARvus=
github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1 h1:ItDcDxUjVLPKja+hogpqgW/kj8LxUL2qscelXIsN1Bs=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1/go.mod h1:DkxMin+qOh1Fgkxfbt+CUfBqqsCQJMG9op8Os/irBPA=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=

@ -3,6 +3,7 @@ package request
import (
"context"
"fmt"
"strconv"
"k8s.io/apiserver/pkg/endpoints/request"
@ -17,8 +18,12 @@ type NamespaceMapper = claims.NamespaceFormatter
// GetNamespaceMapper returns a function that will convert orgIds into a consistent namespace
func GetNamespaceMapper(cfg *setting.Cfg) NamespaceMapper {
if cfg != nil && cfg.StackID != "" {
//val := claims.CloudNamespaceFormatter(cfg.Sta)
return func(orgId int64) string { return "stack-" + cfg.StackID }
stackIdInt, err := strconv.ParseInt(cfg.StackID, 10, 64)
if err != nil {
stackIdInt = 0
}
cloudNamespace := claims.CloudNamespaceFormatter(stackIdInt)
return func(_ int64) string { return cloudNamespace }
}
return claims.OrgNamespaceFormatter
}

@ -26,11 +26,14 @@ func TestNamespaceMapper(t *testing.T) {
orgId: 123,
expected: "org-123",
},
// an invalid use-case, but just documenting that it's handled as stack-0
// this currently prevents the need to have the Mapper return (mapped, err) instead of just mapped.
// err checking is avoided for now to keep the usage fluent
{
name: "with stackId",
cfg: "abc",
orgId: 123, // ignored
expected: "stack-abc",
orgId: 123, // ignored
expected: "stack-0", // we parse to int and default to 0
},
}

@ -8,7 +8,7 @@ import (
"github.com/go-jose/go-jose/v3/jwt"
authlib "github.com/grafana/authlib/authn"
authlibclaims "github.com/grafana/authlib/claims"
"github.com/grafana/authlib/claims"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
@ -74,11 +74,13 @@ type ExtendedJWT struct {
func (s *ExtendedJWT) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) {
jwtToken := s.retrieveAuthenticationToken(r.HTTPRequest)
claims, err := s.accessTokenVerifier.Verify(ctx, jwtToken)
accessToken, err := s.accessTokenVerifier.Verify(ctx, jwtToken)
if err != nil {
return nil, errExtJWTInvalid.Errorf("failed to verify access token: %w", err)
}
accessTokenClaims := authlib.NewAccessClaims(*accessToken)
idToken := s.retrieveAuthorizationToken(r.HTTPRequest)
if idToken != "" {
idTokenClaims, err := s.idTokenVerifier.Verify(ctx, idToken)
@ -86,10 +88,10 @@ func (s *ExtendedJWT) Authenticate(ctx context.Context, r *authn.Request) (*auth
return nil, errExtJWTInvalid.Errorf("failed to verify id token: %w", err)
}
return s.authenticateAsUser(idTokenClaims, claims)
return s.authenticateAsUser(authlib.NewIdentityClaims(*idTokenClaims), accessTokenClaims)
}
return s.authenticateAsService(claims)
return s.authenticateAsService(accessTokenClaims)
}
func (s *ExtendedJWT) IsEnabled() bool {
@ -97,42 +99,42 @@ func (s *ExtendedJWT) IsEnabled() bool {
}
func (s *ExtendedJWT) authenticateAsUser(
idTokenClaims *authlib.Claims[authlib.IDTokenClaims],
accessTokenClaims *authlib.Claims[authlib.AccessTokenClaims],
idTokenClaims claims.IdentityClaims,
accessTokenClaims claims.AccessClaims,
) (*authn.Identity, error) {
// Only allow id tokens signed for namespace configured for this instance.
if allowedNamespace := s.namespaceMapper(s.getDefaultOrgID()); idTokenClaims.Rest.Namespace != allowedNamespace {
return nil, errExtJWTDisallowedNamespaceClaim.Errorf("unexpected id token namespace: %s", idTokenClaims.Rest.Namespace)
if allowedNamespace := s.namespaceMapper(s.getDefaultOrgID()); !claims.NamespaceMatches(idTokenClaims, allowedNamespace) {
return nil, errExtJWTDisallowedNamespaceClaim.Errorf("unexpected id token namespace: %s", idTokenClaims.Namespace())
}
// Allow access tokens with either the same namespace as the validated id token namespace or wildcard (`*`).
if !accessTokenClaims.Rest.NamespaceMatches(idTokenClaims.Rest.Namespace) {
return nil, errExtJWTMisMatchedNamespaceClaims.Errorf("unexpected access token namespace: %s", accessTokenClaims.Rest.Namespace)
if !claims.NamespaceMatches(accessTokenClaims, idTokenClaims.Namespace()) {
return nil, errExtJWTMisMatchedNamespaceClaims.Errorf("unexpected access token namespace: %s", accessTokenClaims.Namespace())
}
accessType, _, err := identity.ParseTypeAndID(accessTokenClaims.Subject)
accessType, _, err := identity.ParseTypeAndID(accessTokenClaims.Subject())
if err != nil {
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", accessTokenClaims.Subject)
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", accessTokenClaims.Subject())
}
if !authlibclaims.IsIdentityType(accessType, authlibclaims.TypeAccessPolicy) {
return nil, errExtJWTInvalid.Errorf("unexpected identity: %s", accessTokenClaims.Subject)
if !claims.IsIdentityType(accessType, claims.TypeAccessPolicy) {
return nil, errExtJWTInvalid.Errorf("unexpected identity: %s", accessTokenClaims.Subject())
}
t, id, err := identity.ParseTypeAndID(idTokenClaims.Subject)
t, id, err := identity.ParseTypeAndID(idTokenClaims.Subject())
if err != nil {
return nil, errExtJWTInvalid.Errorf("failed to parse id token subject: %w", err)
}
if !authlibclaims.IsIdentityType(t, authlibclaims.TypeUser) {
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", idTokenClaims.Subject)
if !claims.IsIdentityType(t, claims.TypeUser) {
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", idTokenClaims.Subject())
}
// For use in service layer, allow higher privilege
allowedKubernetesNamespace := accessTokenClaims.Rest.Namespace
allowedKubernetesNamespace := accessTokenClaims.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
allowedKubernetesNamespace = idTokenClaims.Namespace()
}
return &authn.Identity{
@ -140,30 +142,30 @@ func (s *ExtendedJWT) authenticateAsUser(
Type: t,
OrgID: s.getDefaultOrgID(),
AuthenticatedBy: login.ExtendedJWTModule,
AuthID: accessTokenClaims.Subject,
AuthID: accessTokenClaims.Subject(),
AllowedKubernetesNamespace: allowedKubernetesNamespace,
ClientParams: authn.ClientParams{
SyncPermissions: true,
FetchPermissionsParams: authn.FetchPermissionsParams{
ActionsLookup: accessTokenClaims.Rest.DelegatedPermissions,
ActionsLookup: accessTokenClaims.DelegatedPermissions(),
},
FetchSyncedUser: true,
}}, nil
}
func (s *ExtendedJWT) authenticateAsService(claims *authlib.Claims[authlib.AccessTokenClaims]) (*authn.Identity, error) {
func (s *ExtendedJWT) authenticateAsService(accessTokenClaims claims.AccessClaims) (*authn.Identity, error) {
// Allow access tokens with that has a wildcard namespace or a namespace matching this instance.
if allowedNamespace := s.namespaceMapper(s.getDefaultOrgID()); !claims.Rest.NamespaceMatches(allowedNamespace) {
return nil, errExtJWTDisallowedNamespaceClaim.Errorf("unexpected access token namespace: %s", claims.Rest.Namespace)
if allowedNamespace := s.namespaceMapper(s.getDefaultOrgID()); !claims.NamespaceMatches(accessTokenClaims, allowedNamespace) {
return nil, errExtJWTDisallowedNamespaceClaim.Errorf("unexpected access token namespace: %s", accessTokenClaims.Namespace())
}
t, id, err := identity.ParseTypeAndID(claims.Subject)
t, id, err := identity.ParseTypeAndID(accessTokenClaims.Subject())
if err != nil {
return nil, fmt.Errorf("failed to parse access token subject: %w", err)
}
if !authlibclaims.IsIdentityType(t, authlibclaims.TypeAccessPolicy) {
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", claims.Subject)
if !claims.IsIdentityType(t, claims.TypeAccessPolicy) {
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", accessTokenClaims.Subject())
}
return &authn.Identity{
@ -172,12 +174,12 @@ func (s *ExtendedJWT) authenticateAsService(claims *authlib.Claims[authlib.Acces
Type: t,
OrgID: s.getDefaultOrgID(),
AuthenticatedBy: login.ExtendedJWTModule,
AuthID: claims.Subject,
AllowedKubernetesNamespace: claims.Rest.Namespace,
AuthID: accessTokenClaims.Subject(),
AllowedKubernetesNamespace: accessTokenClaims.Namespace(),
ClientParams: authn.ClientParams{
SyncPermissions: true,
FetchPermissionsParams: authn.FetchPermissionsParams{
Roles: claims.Rest.Permissions,
Roles: accessTokenClaims.Permissions(),
},
FetchSyncedUser: false,
},

@ -51,6 +51,17 @@ var (
},
}
validIDTokenClaimsWithStackSet = idTokenClaims{
Claims: &jwt.Claims{
Subject: "user:2",
Expiry: jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
},
Rest: authnlib.IDTokenClaims{
AuthenticatedBy: "extended_jwt",
Namespace: "stacks-1234",
},
}
validIDTokenClaimsWithDeprecatedStackClaimSet = idTokenClaims{
Claims: &jwt.Claims{
Subject: "user:2",
Expiry: jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
@ -61,7 +72,7 @@ var (
Namespace: "stack-1234",
},
}
validAcessTokenClaimsWildcard = accessTokenClaims{
validAccessTokenClaimsWildcard = accessTokenClaims{
Claims: &jwt.Claims{
Subject: "access-policy:this-uid",
Expiry: jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
@ -71,15 +82,24 @@ var (
Namespace: "*",
},
}
invalidWildcardNamespaceIDTokenClaims = idTokenClaims{
validAccessTokenClaimsWithStackSet = accessTokenClaims{
Claims: &jwt.Claims{
Subject: "user:2",
Subject: "access-policy:this-uid",
Expiry: jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
},
Rest: authnlib.IDTokenClaims{
AuthenticatedBy: "extended_jwt",
Namespace: "*",
Rest: authnlib.AccessTokenClaims{
Namespace: "stacks-1234",
},
}
validAccessTokenClaimsWithDeprecatedStackClaimSet = accessTokenClaims{
Claims: &jwt.Claims{
Subject: "access-policy:this-uid",
Expiry: jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)),
IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)),
},
Rest: authnlib.AccessTokenClaims{
Namespace: "stack-1234",
},
}
invalidNamespaceIDTokenClaims = idTokenClaims{
@ -220,7 +240,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
},
{
name: "should authenticate as service using wildcard namespace",
accessToken: &validAcessTokenClaimsWildcard,
accessToken: &validAccessTokenClaimsWildcard,
orgID: 1,
want: &authn.Identity{
ID: "this-uid",
@ -258,7 +278,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
},
{
name: "should authenticate as user using wildcard namespace for access token",
accessToken: &validAcessTokenClaimsWildcard,
accessToken: &validAccessTokenClaimsWildcard,
idToken: &validIDTokenClaims,
orgID: 1,
want: &authn.Identity{
@ -276,7 +296,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
},
{
name: "should authenticate as user using wildcard namespace for access token, setting allowed namespace to specific",
accessToken: &validAcessTokenClaimsWildcard,
accessToken: &validAccessTokenClaimsWildcard,
idToken: &validIDTokenClaimsWithStackSet,
orgID: 1,
cfg: &setting.Cfg{
@ -291,7 +311,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
ID: "2",
Type: claims.TypeUser,
OrgID: 1,
AllowedKubernetesNamespace: "stack-1234",
AllowedKubernetesNamespace: "stacks-1234",
AuthenticatedBy: "extendedjwt",
AuthID: "access-policy:this-uid",
ClientParams: authn.ClientParams{
@ -301,14 +321,109 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
},
},
{
name: "should return error when id token namespace is a wildcard",
accessToken: &validAccessTokenClaims,
idToken: &invalidWildcardNamespaceIDTokenClaims,
name: "should authenticate as service using specific namespace claim in access token",
accessToken: &validAccessTokenClaimsWithStackSet,
orgID: 1,
wantErr: errExtJWTDisallowedNamespaceClaim,
cfg: &setting.Cfg{
// default org set up by the authenticator is 1
StackID: "1234",
ExtJWTAuth: setting.ExtJWTSettings{
Enabled: true,
ExpectIssuer: "http://localhost:3000",
},
},
want: &authn.Identity{
ID: "this-uid",
UID: "this-uid",
Type: claims.TypeAccessPolicy,
OrgID: 1,
AllowedKubernetesNamespace: "stacks-1234",
AuthenticatedBy: "extendedjwt",
AuthID: "access-policy:this-uid",
ClientParams: authn.ClientParams{
SyncPermissions: true,
},
},
},
{
name: "should authenticate as service using specific deprecated namespace claim in access token",
accessToken: &validAccessTokenClaimsWithDeprecatedStackClaimSet,
orgID: 1,
cfg: &setting.Cfg{
// default org set up by the authenticator is 1
StackID: "1234",
ExtJWTAuth: setting.ExtJWTSettings{
Enabled: true,
ExpectIssuer: "http://localhost:3000",
},
},
want: &authn.Identity{
ID: "this-uid",
UID: "this-uid",
Type: claims.TypeAccessPolicy,
OrgID: 1,
AllowedKubernetesNamespace: "stack-1234",
AuthenticatedBy: "extendedjwt",
AuthID: "access-policy:this-uid",
ClientParams: authn.ClientParams{
SyncPermissions: true,
},
},
},
{
name: "should authenticate as user using specific deprecated namespace claim in access and id tokens",
accessToken: &validAccessTokenClaimsWithDeprecatedStackClaimSet,
idToken: &validIDTokenClaimsWithDeprecatedStackClaimSet,
orgID: 1,
cfg: &setting.Cfg{
// default org set up by the authenticator is 1
StackID: "1234",
ExtJWTAuth: setting.ExtJWTSettings{
Enabled: true,
ExpectIssuer: "http://localhost:3000",
},
},
want: &authn.Identity{
ID: "2",
Type: claims.TypeUser,
OrgID: 1,
AllowedKubernetesNamespace: "stack-1234",
AuthenticatedBy: "extendedjwt",
AuthID: "access-policy:this-uid",
ClientParams: authn.ClientParams{
SyncPermissions: true,
FetchSyncedUser: true,
},
},
},
{
name: "should authenticate as user using wildcard namespace for access token, setting allowed namespace to specific",
accessToken: &validAccessTokenClaimsWildcard,
idToken: &validIDTokenClaimsWithStackSet,
orgID: 1,
cfg: &setting.Cfg{
// default org set up by the authenticator is 1
StackID: "1234",
ExtJWTAuth: setting.ExtJWTSettings{
Enabled: true,
ExpectIssuer: "http://localhost:3000",
},
},
want: &authn.Identity{
ID: "2",
Type: claims.TypeUser,
OrgID: 1,
AllowedKubernetesNamespace: "stacks-1234",
AuthenticatedBy: "extendedjwt",
AuthID: "access-policy:this-uid",
ClientParams: authn.ClientParams{
FetchSyncedUser: true,
SyncPermissions: true,
},
},
},
{
name: "should return error when id token has wildcard namespace",
name: "should return error when id token has an invalid namespace",
accessToken: &validAccessTokenClaims,
idToken: &invalidNamespaceIDTokenClaims,
orgID: 1,

@ -8,6 +8,7 @@ import (
authnlib "github.com/grafana/authlib/authn"
authzlib "github.com/grafana/authlib/authz"
authzv1 "github.com/grafana/authlib/authz/proto/v1"
"github.com/grafana/authlib/claims"
grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
@ -105,7 +106,7 @@ func newInProcLegacyClient(server *legacyServer) (authzlib.MultiTenantClient, er
&authzlib.MultiTenantClientConfig{},
authzlib.WithGrpcConnectionLCOption(channel),
// nolint:staticcheck
authzlib.WithNamespaceFormatterLCOption(authnlib.OnPremNamespaceFormatter),
authzlib.WithNamespaceFormatterLCOption(claims.OrgNamespaceFormatter),
authzlib.WithDisableAccessTokenLCOption(),
)
}
@ -129,7 +130,7 @@ func newGrpcLegacyClient(address string) (authzlib.MultiTenantClient, error) {
grpc.WithStreamInterceptor(clientInterceptor.StreamClientInterceptor),
),
// nolint:staticcheck
authzlib.WithNamespaceFormatterLCOption(authnlib.OnPremNamespaceFormatter),
authzlib.WithNamespaceFormatterLCOption(claims.OrgNamespaceFormatter),
// TODO(drclau): remove this once we have access token support on-prem
authzlib.WithDisableAccessTokenLCOption(),
)

@ -3,7 +3,7 @@ module github.com/grafana/grafana/pkg/storage/unified/apistore
go 1.23.0
require (
github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db
github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240821155123-6891eb1d35da
github.com/grafana/grafana/pkg/apiserver v0.0.0-20240821155123-6891eb1d35da
github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20240821161612-71f0dae39e9d
@ -43,7 +43,7 @@ require (
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db // indirect
github.com/grafana/authlib v0.0.0-20240827201526-24af227df935 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect

@ -161,10 +161,10 @@ github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDP
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db h1:z++X4DdoX+aNlZNT1ZY4cykiFay4+f077pa0AG48SGg=
github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db/go.mod h1:ptt910z9KFfpVSIbSbXvTRR7tS19mxD7EtmVbbJi/WE=
github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db h1:mDk0bwRV6rDrLSmKXftcPf9kLA9uH6EvxJvzpPW9bso=
github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
github.com/grafana/authlib v0.0.0-20240827201526-24af227df935 h1:nT4UY61s2flsiLkU2jDqtqFhOLwqh355+8ZhnavKoMQ=
github.com/grafana/authlib v0.0.0-20240827201526-24af227df935/go.mod h1:ER7bMzNNWTN/5Zl3pwqfgS6XEhcanjrvL7lOp8Ow6oc=
github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd h1:sIlR7n38/MnZvX2qxDEszywXdI5soCwQ78aTDSARvus=
github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240821155123-6891eb1d35da h1:2E3c/I3ayAy4Z1GwIPqXNZcpUccRapE1aBXA1ho4g7o=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240821155123-6891eb1d35da/go.mod h1:p09fvU5ujNL/Ig8HB7g4f+S0zyYbQq3x/f0jA4ujVOM=
github.com/grafana/grafana/pkg/apiserver v0.0.0-20240821155123-6891eb1d35da h1:xQMb8cRZYu7D0IO9q/lB7qFQpLGAoPUnCase1CGHrXY=

@ -4,8 +4,8 @@ go 1.23.0
require (
github.com/fullstorydev/grpchan v1.1.1
github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db
github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db
github.com/grafana/authlib v0.0.0-20240827201526-24af227df935
github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240808164224-787abccfbc9e
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0
github.com/prometheus/client_golang v1.20.0

@ -128,10 +128,10 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfF
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db h1:z++X4DdoX+aNlZNT1ZY4cykiFay4+f077pa0AG48SGg=
github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db/go.mod h1:ptt910z9KFfpVSIbSbXvTRR7tS19mxD7EtmVbbJi/WE=
github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db h1:mDk0bwRV6rDrLSmKXftcPf9kLA9uH6EvxJvzpPW9bso=
github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
github.com/grafana/authlib v0.0.0-20240827201526-24af227df935 h1:nT4UY61s2flsiLkU2jDqtqFhOLwqh355+8ZhnavKoMQ=
github.com/grafana/authlib v0.0.0-20240827201526-24af227df935/go.mod h1:ER7bMzNNWTN/5Zl3pwqfgS6XEhcanjrvL7lOp8Ow6oc=
github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd h1:sIlR7n38/MnZvX2qxDEszywXdI5soCwQ78aTDSARvus=
github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240808164224-787abccfbc9e h1:3vNpomyzv714Hgls5vn+fC0vgv8wUOSHepUl7PB5nUs=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240808164224-787abccfbc9e/go.mod h1:ORVFiW/KNRY52lNjkGwnFWCxNVfE97bJG2jr2fetq0I=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk=

@ -6,7 +6,7 @@ import (
"strconv"
"github.com/grafana/authlib/authn"
"github.com/grafana/authlib/claims"
authClaims "github.com/grafana/authlib/claims"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
@ -77,7 +77,7 @@ func (f *Authenticator) decodeMetadata(ctx context.Context, meta metadata.MD) (i
// TODO, remove after this has been deployed to unified storage
if getter(mdUserID) == "" {
var err error
user.Type = claims.TypeUser
user.Type = authClaims.TypeUser
user.UserID, err = strconv.ParseInt(getter("grafana-userid"), 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid user id: %w", err)

Loading…
Cancel
Save