mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
K8s: namespace mapper should use authlib's util (#92332)
This commit is contained in:
parent
f9719d4ee9
commit
af2e79aa83
6
go.mod
6
go.mod
@ -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
|
||||
|
12
go.sum
12
go.sum
@ -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{
|
||||
@ -287,6 +307,82 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
|
||||
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 authenticate as service using specific namespace claim in access token",
|
||||
accessToken: &validAccessTokenClaimsWithStackSet,
|
||||
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: "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,
|
||||
@ -294,6 +390,32 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
|
||||
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,
|
||||
@ -301,14 +423,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should return error when id token namespace is a wildcard",
|
||||
accessToken: &validAccessTokenClaims,
|
||||
idToken: &invalidWildcardNamespaceIDTokenClaims,
|
||||
orgID: 1,
|
||||
wantErr: errExtJWTDisallowedNamespaceClaim,
|
||||
},
|
||||
{
|
||||
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…
Reference in New Issue
Block a user