From 243c0935fcd1fa14d82710491eba9cf40f0632ca Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 9 Aug 2024 19:46:56 +0300 Subject: [PATCH] Auth: Use claims.AuthInfo in requester (#91739) --- go.mod | 5 +- go.sum | 6 +- go.work.sum | 4 +- pkg/apimachinery/go.mod | 3 +- pkg/apimachinery/go.sum | 6 +- pkg/apimachinery/identity/context.go | 3 + pkg/apimachinery/identity/requester.go | 2 + pkg/apimachinery/identity/static.go | 14 ++ pkg/apimachinery/identity/wrapper.go | 115 ++++++++++++++++ pkg/registry/apis/dashboard/authorizer.go | 4 +- pkg/registry/apis/dashboard/legacy/storage.go | 10 +- .../apis/dashboardsnapshot/register.go | 3 +- .../apiserver/auth/authorizer/org_id.go | 6 +- .../apiserver/auth/authorizer/stack_id.go | 13 +- .../apiserver/endpoints/request/namespace.go | 62 +-------- .../endpoints/request/namespace_test.go | 128 ------------------ pkg/services/authn/identity.go | 14 ++ pkg/services/authz/client.go | 2 + pkg/services/user/identity.go | 14 ++ 19 files changed, 207 insertions(+), 207 deletions(-) create mode 100644 pkg/apimachinery/identity/wrapper.go diff --git a/go.mod b/go.mod index 0205da27957..647fa4bfba9 100644 --- a/go.mod +++ b/go.mod @@ -75,7 +75,8 @@ require ( 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-20240723124849-f2ab7c7b8f7d // @grafana/alerting-backend - github.com/grafana/authlib v0.0.0-20240730122259-a0d13672efb1 // @grafana/identity-access-team + github.com/grafana/authlib v0.0.0-20240809101159-74eaccc31a06 // @grafana/identity-access-team + github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06 // @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 @@ -475,7 +476,7 @@ require ( ) require ( - github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect; indirect0.0.0-20240809095826-8eb5495c0b2a github.com/x448/float16 v0.8.4 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect ) diff --git a/go.sum b/go.sum index c6a9a1b7f28..8c530194769 100644 --- a/go.sum +++ b/go.sum @@ -2309,8 +2309,10 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/alerting v0.0.0-20240723124849-f2ab7c7b8f7d h1:d2NZeTs+zBPVMd8uOOV5+6lyfs0BCDKxtiNxIMjnPNA= github.com/grafana/alerting v0.0.0-20240723124849-f2ab7c7b8f7d/go.mod h1:DLj8frbtCaITljC2jc0L85JQViPF3mPfOSiYhm1osso= -github.com/grafana/authlib v0.0.0-20240730122259-a0d13672efb1 h1:EiaupmOnt6XF/LPxvagjTofWmByzYaf5VyMIF+w/71M= -github.com/grafana/authlib v0.0.0-20240730122259-a0d13672efb1/go.mod h1:YA9We4kTafu7mlMnUh3In6Q2wpg8fYN3ycgCKOK1TB8= +github.com/grafana/authlib v0.0.0-20240809101159-74eaccc31a06 h1:qks7nEo/A0+mWvjMjWEIfFD9eIVipb5Lxjfg+HcB5u4= +github.com/grafana/authlib v0.0.0-20240809101159-74eaccc31a06/go.mod h1:5uu+ADz2c8bVsXheavXS735IcDuO6M3dr+evuDl8rIE= +github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06 h1:uD1LcKwvEAqzDsgVChBudPqo5BhPxkj9AgylT5QCReo= +github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06/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= diff --git a/go.work.sum b/go.work.sum index aa14204571b..380c075323b 100644 --- a/go.work.sum +++ b/go.work.sum @@ -29,6 +29,7 @@ cloud.google.com/go/channel v1.17.5 h1:/omiBnyFjm4S1ETHoOmJbL7LH7Ljcei4rYG6Sj3hc cloud.google.com/go/cloudbuild v1.15.1 h1:ZB6oOmJo+MTov9n629fiCrO9YZPOg25FZvQ7gIHu5ng= cloud.google.com/go/clouddms v1.7.4 h1:Sr0Zo5EAcPQiCBgHWICg3VGkcdS/LLP1d9SR7qQBM/s= cloud.google.com/go/cloudtasks v1.12.6 h1:EUt1hIZ9bLv8Iz9yWaCrqgMnIU+Tdh0yXM1MMVGhjfE= +cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= cloud.google.com/go/contactcenterinsights v1.13.0 h1:6Vs/YnDG5STGjlWMEjN/xtmft7MrOTOnOZYUZtGTx0w= cloud.google.com/go/container v1.31.0 h1:MAaNH7VRNPWEhvqOypq2j+7ONJKrKzon4v9nS3nLZe0= @@ -408,8 +409,8 @@ github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv github.com/grafana/alerting v0.0.0-20240712142914-5558735b4462 h1:MWpvVoPcSej4YfxSIuAllr9vg0UgVEG5CQifD5fK+ps= github.com/grafana/alerting v0.0.0-20240712142914-5558735b4462/go.mod h1:DLj8frbtCaITljC2jc0L85JQViPF3mPfOSiYhm1osso= github.com/grafana/authlib v0.0.0-20240611075137-331cbe4e840f/go.mod h1:+MjD5sxxgLOIvw0ox18wJmjBzz8tOECo7quiiZAmgJY= +github.com/grafana/authlib/claims v0.0.0-20240809095826-8eb5495c0b2a/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= github.com/grafana/grafana-plugin-sdk-go v0.235.0/go.mod h1:6n9LbrjGL3xAATntYVNcIi90G9BVHRJjzHKz5FXVfWw= -github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1/go.mod h1:DkxMin+qOh1Fgkxfbt+CUfBqqsCQJMG9op8Os/irBPA= github.com/grafana/prometheus-alertmanager v0.25.1-0.20240422145632-c33c6b5b6e6b h1:HCbWyVL6vi7gxyO76gQksSPH203oBJ1MJ3JcG1OQlsg= github.com/grafana/prometheus-alertmanager v0.25.1-0.20240422145632-c33c6b5b6e6b/go.mod h1:01sXtHoRwI8W324IPAzuxDFOmALqYLCOhvSC2fUHWXc= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= @@ -864,7 +865,6 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c/go. google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas= google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= -google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240325203815-454cdb8f5daa h1:wBkzraZsSqhj1M4L/nMrljUU6XasJkgHvUsq8oRGwF0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index bae4b20e921..2c487cb170d 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -3,7 +3,8 @@ module github.com/grafana/grafana/pkg/apimachinery go 1.22.4 require ( - github.com/grafana/authlib v0.0.0-20240730122259-a0d13672efb1 + github.com/grafana/authlib v0.0.0-20240809101159-74eaccc31a06 // @grafana/identity-access-team + github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06 // @grafana/identity-access-team github.com/stretchr/testify v1.9.0 k8s.io/apimachinery v0.31.0-rc.1 k8s.io/apiserver v0.31.0-rc.1 diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 9e9e46a31a6..167e13d1954 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -28,8 +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-20240730122259-a0d13672efb1 h1:EiaupmOnt6XF/LPxvagjTofWmByzYaf5VyMIF+w/71M= -github.com/grafana/authlib v0.0.0-20240730122259-a0d13672efb1/go.mod h1:YA9We4kTafu7mlMnUh3In6Q2wpg8fYN3ycgCKOK1TB8= +github.com/grafana/authlib v0.0.0-20240809101159-74eaccc31a06 h1:qks7nEo/A0+mWvjMjWEIfFD9eIVipb5Lxjfg+HcB5u4= +github.com/grafana/authlib v0.0.0-20240809101159-74eaccc31a06/go.mod h1:5uu+ADz2c8bVsXheavXS735IcDuO6M3dr+evuDl8rIE= +github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06 h1:uD1LcKwvEAqzDsgVChBudPqo5BhPxkj9AgylT5QCReo= +github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06/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= diff --git a/pkg/apimachinery/identity/context.go b/pkg/apimachinery/identity/context.go index b402209497b..a88964ac66f 100644 --- a/pkg/apimachinery/identity/context.go +++ b/pkg/apimachinery/identity/context.go @@ -4,12 +4,15 @@ import ( "context" "fmt" "reflect" + + "github.com/grafana/authlib/claims" ) type ctxUserKey struct{} // WithRequester attaches the requester to the context. func WithRequester(ctx context.Context, usr Requester) context.Context { + ctx = claims.WithClaims(ctx, usr) // also set the upstream auth info claims return context.WithValue(ctx, ctxUserKey{}, usr) } diff --git a/pkg/apimachinery/identity/requester.go b/pkg/apimachinery/identity/requester.go index 0a4e1dffa7f..c8f110a5a2e 100644 --- a/pkg/apimachinery/identity/requester.go +++ b/pkg/apimachinery/identity/requester.go @@ -5,11 +5,13 @@ import ( "strconv" authnlib "github.com/grafana/authlib/authn" + "github.com/grafana/authlib/claims" "k8s.io/apiserver/pkg/authentication/user" ) type Requester interface { user.Info + claims.AuthInfo // GetIdentityType returns the type for the requester GetIdentityType() IdentityType diff --git a/pkg/apimachinery/identity/static.go b/pkg/apimachinery/identity/static.go index 6ed1bae9e28..3fc5aec3430 100644 --- a/pkg/apimachinery/identity/static.go +++ b/pkg/apimachinery/identity/static.go @@ -4,6 +4,7 @@ import ( "fmt" authnlib "github.com/grafana/authlib/authn" + "github.com/grafana/authlib/claims" ) var _ Requester = &StaticRequester{} @@ -35,6 +36,19 @@ type StaticRequester struct { CacheKey string } +// Access implements Requester. +func (u *StaticRequester) GetAccess() claims.AccessClaims { + return &IDClaimsWrapper{Source: u} +} + +// Identity implements Requester. +func (u *StaticRequester) GetIdentity() claims.IdentityClaims { + if u.IDTokenClaims != nil { + return authnlib.NewIdentityClaims(*u.IDTokenClaims) + } + return &IDClaimsWrapper{Source: u} +} + // GetRawIdentifier implements Requester. func (u *StaticRequester) GetUID() string { return fmt.Sprintf("%s:%s", u.Type, u.UserUID) diff --git a/pkg/apimachinery/identity/wrapper.go b/pkg/apimachinery/identity/wrapper.go new file mode 100644 index 00000000000..07e2b3b95e1 --- /dev/null +++ b/pkg/apimachinery/identity/wrapper.go @@ -0,0 +1,115 @@ +package identity + +import ( + "time" + + "github.com/grafana/authlib/claims" +) + +var _ claims.IdentityClaims = &IDClaimsWrapper{} +var _ claims.AccessClaims = &IDClaimsWrapper{} + +type IDClaimsWrapper struct { + Source Requester +} + +// GetAuthenticatedBy implements claims.IdentityClaims. +func (i *IDClaimsWrapper) AuthenticatedBy() string { + return i.Source.GetAuthenticatedBy() +} + +// GetDisplayName implements claims.IdentityClaims. +func (i *IDClaimsWrapper) DisplayName() string { + return i.Source.GetDisplayName() +} + +// GetEmail implements claims.IdentityClaims. +func (i *IDClaimsWrapper) Email() string { + return i.Source.GetEmail() +} + +// GetEmailVerified implements claims.IdentityClaims. +func (i *IDClaimsWrapper) EmailVerified() bool { + return i.Source.IsEmailVerified() +} + +// GetIdentityType implements claims.IdentityClaims. +func (i *IDClaimsWrapper) IdentityType() claims.IdentityType { + return claims.IdentityType(i.Source.GetIdentityType()) +} + +// GetInternalID implements claims.IdentityClaims. +func (i *IDClaimsWrapper) InternalID() int64 { + v, _ := i.Source.GetInternalID() + return v +} + +// GetOrgID implements claims.IdentityClaims. +func (i *IDClaimsWrapper) OrgID() int64 { + return i.Source.GetOrgID() +} + +// GetRawUID implements claims.IdentityClaims. +func (i *IDClaimsWrapper) UID() string { + return i.Source.GetRawIdentifier() +} + +// GetUsername implements claims.IdentityClaims. +func (i *IDClaimsWrapper) Username() string { + return i.Source.GetLogin() +} + +// GetAudience implements claims.AccessClaims. +func (i *IDClaimsWrapper) Audience() []string { + return []string{} +} + +// GetDelegatedPermissions implements claims.AccessClaims. +func (i *IDClaimsWrapper) DelegatedPermissions() []string { + return []string{} +} + +// GetExpiry implements claims.AccessClaims. +func (i *IDClaimsWrapper) Expiry() *time.Time { + return nil +} + +// GetIssuedAt implements claims.AccessClaims. +func (i *IDClaimsWrapper) IssuedAt() *time.Time { + return nil +} + +// GetIssuer implements claims.AccessClaims. +func (i *IDClaimsWrapper) Issuer() string { + return "" +} + +// GetJTI implements claims.AccessClaims. +func (i *IDClaimsWrapper) JTI() string { + return "" +} + +// GetNamespace implements claims.AccessClaims. +func (i *IDClaimsWrapper) Namespace() string { + return i.Source.GetAllowedKubernetesNamespace() +} + +// GetNotBefore implements claims.AccessClaims. +func (i *IDClaimsWrapper) NotBefore() *time.Time { + return nil +} + +// GetPermissions implements claims.AccessClaims. +func (i *IDClaimsWrapper) Permissions() []string { + return []string{} +} + +// GetScopes implements claims.AccessClaims. +func (i *IDClaimsWrapper) Scopes() []string { + return []string{} +} + +// GetSubject implements claims.AccessClaims. +func (i *IDClaimsWrapper) Subject() string { + return "" +} diff --git a/pkg/registry/apis/dashboard/authorizer.go b/pkg/registry/apis/dashboard/authorizer.go index f98c57d1383..b3fe891e4c6 100644 --- a/pkg/registry/apis/dashboard/authorizer.go +++ b/pkg/registry/apis/dashboard/authorizer.go @@ -5,9 +5,9 @@ import ( "k8s.io/apiserver/pkg/authorization/authorizer" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/guardian" ) @@ -39,7 +39,7 @@ func (b *DashboardsAPIBuilder) GetAuthorizer() authorizer.Authorizer { return authorizer.DecisionDeny, "expected namespace", nil } - info, err := request.ParseNamespace(attr.GetNamespace()) + info, err := claims.ParseNamespace(attr.GetNamespace()) if err != nil { return authorizer.DecisionDeny, "error reading org from namespace", err } diff --git a/pkg/registry/apis/dashboard/legacy/storage.go b/pkg/registry/apis/dashboard/legacy/storage.go index 837b86b6fb1..f42a41b666c 100644 --- a/pkg/registry/apis/dashboard/legacy/storage.go +++ b/pkg/registry/apis/dashboard/legacy/storage.go @@ -9,9 +9,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/utils" dashboard "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/storage/unified/resource" ) @@ -43,7 +43,7 @@ func isDashboardKey(key *resource.ResourceKey, requireName bool) error { } func (a *dashboardSqlAccess) WriteEvent(ctx context.Context, event resource.WriteEvent) (rv int64, err error) { - info, err := request.ParseNamespace(event.Key.Namespace) + info, err := claims.ParseNamespace(event.Key.Namespace) if err == nil { err = isDashboardKey(event.Key, true) } @@ -125,7 +125,7 @@ func (a *dashboardSqlAccess) GetDashboard(ctx context.Context, orgId int64, uid // Read implements ResourceStoreServer. func (a *dashboardSqlAccess) ReadResource(ctx context.Context, req *resource.ReadRequest) *resource.ReadResponse { rsp := &resource.ReadResponse{} - info, err := request.ParseNamespace(req.Key.Namespace) + info, err := claims.ParseNamespace(req.Key.Namespace) if err == nil { err = isDashboardKey(req.Key, true) } @@ -160,7 +160,7 @@ func (a *dashboardSqlAccess) ReadResource(ctx context.Context, req *resource.Rea // List implements AppendingStore. func (a *dashboardSqlAccess) ListIterator(ctx context.Context, req *resource.ListRequest, cb func(resource.ListIterator) error) (int64, error) { opts := req.Options - info, err := request.ParseNamespace(opts.Key.Namespace) + info, err := claims.ParseNamespace(opts.Key.Namespace) if err == nil { err = isDashboardKey(opts.Key, false) } @@ -237,7 +237,7 @@ func (a *dashboardSqlAccess) Read(ctx context.Context, req *resource.ReadRequest } func (a *dashboardSqlAccess) History(ctx context.Context, req *resource.HistoryRequest) (*resource.HistoryResponse, error) { - info, err := request.ParseNamespace(req.Key.Namespace) + info, err := claims.ParseNamespace(req.Key.Namespace) if err == nil { err = isDashboardKey(req.Key, false) } diff --git a/pkg/registry/apis/dashboardsnapshot/register.go b/pkg/registry/apis/dashboardsnapshot/register.go index 611d7d91fc6..d5c9c557e62 100644 --- a/pkg/registry/apis/dashboardsnapshot/register.go +++ b/pkg/registry/apis/dashboardsnapshot/register.go @@ -20,6 +20,7 @@ import ( "k8s.io/kube-openapi/pkg/spec3" "k8s.io/kube-openapi/pkg/validation/spec" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" dashboardsnapshot "github.com/grafana/grafana/pkg/apis/dashboardsnapshot/v0alpha1" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" @@ -242,7 +243,7 @@ func (b *SnapshotsAPIBuilder) GetAPIRoutes() *builder.APIRoutes { } vars := mux.Vars(r) - info, err := request.ParseNamespace(vars["namespace"]) + info, err := claims.ParseNamespace(vars["namespace"]) if err != nil { wrap.JsonApiErr(http.StatusBadRequest, "expected namespace", nil) return diff --git a/pkg/services/apiserver/auth/authorizer/org_id.go b/pkg/services/apiserver/auth/authorizer/org_id.go index b9425a585c8..f35bbce484e 100644 --- a/pkg/services/apiserver/auth/authorizer/org_id.go +++ b/pkg/services/apiserver/auth/authorizer/org_id.go @@ -4,9 +4,9 @@ import ( "context" "fmt" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" - grafanarequest "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/org" "k8s.io/apiserver/pkg/authorization/authorizer" ) @@ -31,7 +31,7 @@ func (auth orgIDAuthorizer) Authorize(ctx context.Context, a authorizer.Attribut return authorizer.DecisionDeny, fmt.Sprintf("error getting signed in user: %v", err), nil } - info, err := grafanarequest.ParseNamespace(a.GetNamespace()) + info, err := claims.ParseNamespace(a.GetNamespace()) if err != nil { return authorizer.DecisionDeny, fmt.Sprintf("error reading namespace: %v", err), nil } @@ -45,7 +45,7 @@ func (auth orgIDAuthorizer) Authorize(ctx context.Context, a authorizer.Attribut return authorizer.DecisionDeny, "org id is required", nil } - if info.StackID != "" { + if info.StackID != 0 { return authorizer.DecisionDeny, "using a stack namespace requires deployment with a fixed stack id", nil } diff --git a/pkg/services/apiserver/auth/authorizer/stack_id.go b/pkg/services/apiserver/auth/authorizer/stack_id.go index da24f890aae..c31e0c3f62b 100644 --- a/pkg/services/apiserver/auth/authorizer/stack_id.go +++ b/pkg/services/apiserver/auth/authorizer/stack_id.go @@ -3,10 +3,11 @@ package authorizer import ( "context" "fmt" + "strconv" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" - grafanarequest "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/setting" "k8s.io/apiserver/pkg/authorization/authorizer" ) @@ -15,13 +16,17 @@ var _ authorizer.Authorizer = &stackIDAuthorizer{} type stackIDAuthorizer struct { log log.Logger - stackID string + stackID int64 } func newStackIDAuthorizer(cfg *setting.Cfg) *stackIDAuthorizer { + stackID, err := strconv.ParseInt(cfg.StackID, 10, 64) + if err != nil { + return nil + } return &stackIDAuthorizer{ log: log.New("grafana-apiserver.authorizer.stackid"), - stackID: cfg.StackID, // this lets a single tenant grafana validate stack id (rather than orgs) + stackID: stackID, // this lets a single tenant grafana validate stack id (rather than orgs) } } @@ -31,7 +36,7 @@ func (auth stackIDAuthorizer) Authorize(ctx context.Context, a authorizer.Attrib return authorizer.DecisionDeny, fmt.Sprintf("error getting signed in user: %v", err), nil } - info, err := grafanarequest.ParseNamespace(a.GetNamespace()) + info, err := claims.ParseNamespace(a.GetNamespace()) if err != nil { return authorizer.DecisionDeny, fmt.Sprintf("error reading namespace: %v", err), nil } diff --git a/pkg/services/apiserver/endpoints/request/namespace.go b/pkg/services/apiserver/endpoints/request/namespace.go index 2a4c768673b..285b5e40273 100644 --- a/pkg/services/apiserver/endpoints/request/namespace.go +++ b/pkg/services/apiserver/endpoints/request/namespace.go @@ -3,82 +3,34 @@ package request import ( "context" "fmt" - "strconv" - "strings" "k8s.io/apiserver/pkg/endpoints/request" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/setting" ) -type NamespaceInfo struct { - // OrgID defined in namespace (1 when using stack ids) - OrgID int64 - - // The cloud stack ID (must match the value in cfg.Settings) - StackID string - - // The original namespace string regardless the input - Value string -} - // NamespaceMapper converts an orgID into a namespace -type NamespaceMapper = func(orgId int64) string +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 } } - return func(orgId int64) string { - if orgId == 1 { - return "default" - } - return fmt.Sprintf("org-%d", orgId) - } + return claims.OrgNamespaceFormatter } -func NamespaceInfoFrom(ctx context.Context, requireOrgID bool) (NamespaceInfo, error) { - info, err := ParseNamespace(request.NamespaceValue(ctx)) +func NamespaceInfoFrom(ctx context.Context, requireOrgID bool) (claims.NamespaceInfo, error) { + info, err := claims.ParseNamespace(request.NamespaceValue(ctx)) if err == nil && requireOrgID && info.OrgID < 1 { return info, fmt.Errorf("expected valid orgId in namespace") } return info, err } -func ParseNamespace(ns string) (NamespaceInfo, error) { - info := NamespaceInfo{Value: ns, OrgID: -1} - if ns == "default" { - info.OrgID = 1 - return info, nil - } - - if strings.HasPrefix(ns, "org-") { - id, err := strconv.Atoi(ns[4:]) - if id < 1 { - return info, fmt.Errorf("invalid org id") - } - if id == 1 { - return info, fmt.Errorf("use default rather than org-1") - } - info.OrgID = int64(id) - return info, err - } - - if strings.HasPrefix(ns, "stack-") { - stackIDStr := ns[6:] - stackID, err := strconv.Atoi(stackIDStr) - if err != nil || stackID < 1 { - return info, fmt.Errorf("invalid stack id") - } - info.StackID = stackIDStr - info.OrgID = 1 - return info, nil - } - return info, nil -} - func OrgIDForList(ctx context.Context) (int64, error) { ns := request.NamespaceValue(ctx) if ns == "" { @@ -88,6 +40,6 @@ func OrgIDForList(ctx context.Context) (int64, error) { } return -1, err } - info, err := ParseNamespace(ns) + info, err := claims.ParseNamespace(ns) return info.OrgID, err } diff --git a/pkg/services/apiserver/endpoints/request/namespace_test.go b/pkg/services/apiserver/endpoints/request/namespace_test.go index f5665ff2f5b..a42be2605d4 100644 --- a/pkg/services/apiserver/endpoints/request/namespace_test.go +++ b/pkg/services/apiserver/endpoints/request/namespace_test.go @@ -9,134 +9,6 @@ import ( "github.com/grafana/grafana/pkg/setting" ) -func TestParseNamespace(t *testing.T) { - tests := []struct { - name string - namespace string - expected request.NamespaceInfo - expectErr bool - }{ - { - name: "empty namespace", - expected: request.NamespaceInfo{ - OrgID: -1, - }, - }, - { - name: "incorrect number of parts", - namespace: "org-123-a", - expectErr: true, - expected: request.NamespaceInfo{ - OrgID: -1, - }, - }, - { - name: "org id not a number", - namespace: "org-invalid", - expectErr: true, - expected: request.NamespaceInfo{ - OrgID: -1, - }, - }, - { - name: "valid org id", - namespace: "org-123", - expected: request.NamespaceInfo{ - OrgID: 123, - }, - }, - { - name: "org should not be 1 in the namespace", - namespace: "org-1", - expectErr: true, - expected: request.NamespaceInfo{ - OrgID: -1, - }, - }, - { - name: "can not be negative", - namespace: "org--5", - expectErr: true, - expected: request.NamespaceInfo{ - OrgID: -1, - }, - }, - { - name: "can not be zero", - namespace: "org-0", - expectErr: true, - expected: request.NamespaceInfo{ - OrgID: -1, - }, - }, - { - name: "default is org 1", - namespace: "default", - expected: request.NamespaceInfo{ - OrgID: 1, - }, - }, - { - name: "invalid stack id (must be an int)", - expectErr: true, - namespace: "stack-abcdef", - expected: request.NamespaceInfo{ - OrgID: -1, - }, - }, - { - name: "invalid stack id (must be provided)", - namespace: "stack-", - expectErr: true, - expected: request.NamespaceInfo{ - OrgID: -1, - }, - }, - { - name: "invalid stack id (cannot be 0)", - namespace: "stack-0", - expectErr: true, - expected: request.NamespaceInfo{ - OrgID: -1, - }, - }, - { - name: "valid stack", - namespace: "stack-1", - expected: request.NamespaceInfo{ - OrgID: 1, - StackID: "1", - }, - }, - { - name: "other namespace", - namespace: "anything", - expected: request.NamespaceInfo{ - OrgID: -1, - Value: "anything", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - info, err := request.ParseNamespace(tt.namespace) - if tt.expectErr != (err != nil) { - t.Errorf("ParseNamespace() returned %+v, expected an error", info) - } - if info.OrgID != tt.expected.OrgID { - t.Errorf("ParseNamespace() [OrgID] returned %d, expected %d", info.OrgID, tt.expected.OrgID) - } - if info.StackID != tt.expected.StackID { - t.Errorf("ParseNamespace() [StackID] returned %s, expected %s", info.StackID, tt.expected.StackID) - } - if info.Value != tt.namespace { - t.Errorf("ParseNamespace() [Value] returned %s, expected %s", info.Value, tt.namespace) - } - }) - } -} - func TestNamespaceMapper(t *testing.T) { tests := []struct { name string diff --git a/pkg/services/authn/identity.go b/pkg/services/authn/identity.go index 761056891b5..a1b9da18c62 100644 --- a/pkg/services/authn/identity.go +++ b/pkg/services/authn/identity.go @@ -5,6 +5,7 @@ import ( "time" "github.com/grafana/authlib/authn" + "github.com/grafana/authlib/claims" "golang.org/x/oauth2" "github.com/grafana/grafana/pkg/apimachinery/identity" @@ -74,6 +75,19 @@ type Identity struct { IDTokenClaims *authn.Claims[authn.IDTokenClaims] } +// Access implements claims.AuthInfo. +func (i *Identity) GetAccess() claims.AccessClaims { + return &identity.IDClaimsWrapper{Source: i} +} + +// Identity implements claims.AuthInfo. +func (i *Identity) GetIdentity() claims.IdentityClaims { + if i.IDTokenClaims != nil { + return authn.NewIdentityClaims(*i.IDTokenClaims) + } + return &identity.IDClaimsWrapper{Source: i} +} + // GetRawIdentifier implements Requester. func (i *Identity) GetRawIdentifier() string { return i.UID.ID() diff --git a/pkg/services/authz/client.go b/pkg/services/authz/client.go index dc6ef3aa514..69f68f5cf94 100644 --- a/pkg/services/authz/client.go +++ b/pkg/services/authz/client.go @@ -104,6 +104,7 @@ func newInProcLegacyClient(server *legacyServer) (authzlib.MultiTenantClient, er return authzlib.NewLegacyClient( &authzlib.MultiTenantClientConfig{}, authzlib.WithGrpcConnectionLCOption(channel), + // nolint:staticcheck authzlib.WithNamespaceFormatterLCOption(authnlib.OnPremNamespaceFormatter), authzlib.WithDisableAccessTokenLCOption(), ) @@ -127,6 +128,7 @@ func newGrpcLegacyClient(address string) (authzlib.MultiTenantClient, error) { grpc.WithUnaryInterceptor(clientInterceptor.UnaryClientInterceptor), grpc.WithStreamInterceptor(clientInterceptor.StreamClientInterceptor), ), + // nolint:staticcheck authzlib.WithNamespaceFormatterLCOption(authnlib.OnPremNamespaceFormatter), // TODO(drclau): remove this once we have access token support on-prem authzlib.WithDisableAccessTokenLCOption(), diff --git a/pkg/services/user/identity.go b/pkg/services/user/identity.go index 45d4c7c4e60..37671cd2fe3 100644 --- a/pkg/services/user/identity.go +++ b/pkg/services/user/identity.go @@ -6,6 +6,7 @@ import ( "time" authnlib "github.com/grafana/authlib/authn" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" ) @@ -52,6 +53,19 @@ type SignedInUser struct { FallbackType identity.IdentityType } +// Access implements claims.AuthInfo. +func (u *SignedInUser) GetAccess() claims.AccessClaims { + return &identity.IDClaimsWrapper{Source: u} +} + +// Identity implements claims.AuthInfo. +func (u *SignedInUser) GetIdentity() claims.IdentityClaims { + if u.IDTokenClaims != nil { + return authnlib.NewIdentityClaims(*u.IDTokenClaims) + } + return &identity.IDClaimsWrapper{Source: u} +} + // GetRawIdentifier implements Requester. func (u *SignedInUser) GetRawIdentifier() string { if u.UserUID == "" {