K8s: use contexthandler in standalone handler chain (#90102)

pull/90210/head
Charandas 12 months ago committed by GitHub
parent 3b6a8775bb
commit c210617735
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      pkg/api/common_test.go
  2. 3
      pkg/apimachinery/identity/requester.go
  3. 33
      pkg/apimachinery/identity/static.go
  4. 17
      pkg/apiserver/endpoints/filters/requester.go
  5. 10
      pkg/cmd/grafana/apiserver/cmd.go
  6. 3
      pkg/cmd/grafana/apiserver/server.go
  7. 2
      pkg/middleware/auth_test.go
  8. 3
      pkg/middleware/middleware_test.go
  9. 1
      pkg/server/wire.go
  10. 10
      pkg/services/apiserver/builder/helper.go
  11. 8
      pkg/services/apiserver/standalone/factory.go
  12. 9
      pkg/services/authn/authn.go
  13. 5
      pkg/services/authn/authnimpl/service.go
  14. 38
      pkg/services/authn/clients/ext_jwt.go
  15. 81
      pkg/services/authn/clients/ext_jwt_test.go
  16. 6
      pkg/services/authn/identity.go
  17. 27
      pkg/services/contexthandler/contexthandler.go
  18. 7
      pkg/services/contexthandler/contexthandler_test.go
  19. 8
      pkg/services/user/identity.go
  20. 8
      pkg/setting/setting_jwt.go
  21. 11
      pkg/web/macaron.go

@ -189,7 +189,6 @@ func getContextHandler(t *testing.T, cfg *setting.Cfg) *contexthandler.ContextHa
return contexthandler.ProvideService(
cfg,
tracing.InitializeTracerForTest(),
featuremgmt.WithFeatures(),
&authntest.FakeService{ExpectedIdentity: &authn.Identity{ID: authn.AnonymousNamespaceID, SessionToken: &usertoken.UserToken{}}},
)
}

@ -43,6 +43,9 @@ type Requester interface {
GetOrgName() string
// GetAuthID returns external id for entity.
GetAuthID() string
// GetAllowedKubernetesNamespace returns either "*" or the single namespace this requester has access to
// An empty value means the implementation has not specified a kubernetes namespace.
GetAllowedKubernetesNamespace() string
// GetAuthenticatedBy returns the authentication method used to authenticate the entity.
GetAuthenticatedBy() string
// IsAuthenticatedBy returns true if entity was authenticated by any of supplied providers.

@ -9,20 +9,21 @@ var _ Requester = &StaticRequester{}
// This is mostly copied from:
// https://github.com/grafana/grafana/blob/v11.0.0/pkg/services/user/identity.go#L16
type StaticRequester struct {
Namespace Namespace
UserID int64
UserUID string
OrgID int64
OrgName string
OrgRole RoleType
Login string
Name string
DisplayName string
Email string
EmailVerified bool
AuthID string
AuthenticatedBy string
IsGrafanaAdmin bool
Namespace Namespace
UserID int64
UserUID string
OrgID int64
OrgName string
OrgRole RoleType
Login string
Name string
DisplayName string
Email string
EmailVerified bool
AuthID string
AuthenticatedBy string
AllowedKubernetesNamespace string
IsGrafanaAdmin bool
// Permissions grouped by orgID and actions
Permissions map[int64]map[string][]string
IDToken string
@ -123,6 +124,10 @@ func (u *StaticRequester) GetAuthID() string {
return u.AuthID
}
func (u *StaticRequester) GetAllowedKubernetesNamespace() string {
return u.AllowedKubernetesNamespace
}
func (u *StaticRequester) GetAuthenticatedBy() string {
return u.AuthenticatedBy
}

@ -33,13 +33,16 @@ func WithRequester(handler http.Handler) http.Handler {
slices.Contains(info.GetGroups(), user.SystemPrivilegedGroup) {
orgId := int64(1)
requester = &identity.StaticRequester{
Namespace: identity.NamespaceServiceAccount, // system:apiserver
UserID: 1,
OrgID: orgId,
Name: info.GetName(),
Login: info.GetName(),
OrgRole: identity.RoleAdmin,
IsGrafanaAdmin: true,
Namespace: identity.NamespaceServiceAccount, // system:apiserver
UserID: 1,
OrgID: orgId,
Name: info.GetName(),
Login: info.GetName(),
OrgRole: identity.RoleAdmin,
IsGrafanaAdmin: true,
AllowedKubernetesNamespace: "default",
Permissions: map[int64]map[string][]string{
orgId: {
"*": {"*"}, // all resources, all scopes

@ -74,15 +74,15 @@ func newCommandStartExampleAPIServer(o *APIServerOptions, stopCh <-chan struct{}
return err
}
config, err := o.Config()
if err != nil {
return err
}
if o.Options.TracingOptions.TracingService != nil {
tracer.InitTracer(o.Options.TracingOptions.TracingService)
}
config, err := o.Config(tracer)
if err != nil {
return err
}
defer o.factory.Shutdown()
if err := o.RunAPIServer(config, stopCh); err != nil {

@ -76,7 +76,7 @@ func (o *APIServerOptions) loadAPIGroupBuilders(ctx context.Context, tracer trac
return nil
}
func (o *APIServerOptions) Config() (*genericapiserver.RecommendedConfig, error) {
func (o *APIServerOptions) Config(tracer tracing.Tracer) (*genericapiserver.RecommendedConfig, error) {
if err := o.Options.RecommendedOptions.SecureServing.MaybeDefaultWithSelfSignedCerts(
"localhost", o.AlternateDNS, []net.IP{netutils.ParseIPSloppy("127.0.0.1")},
); err != nil {
@ -122,6 +122,7 @@ func (o *APIServerOptions) Config() (*genericapiserver.RecommendedConfig, error)
setting.BuildVersion,
setting.BuildCommit,
setting.BuildBranch,
o.factory.GetOptionalMiddlewares(tracer)...,
)
return serverConfig, err
}

@ -26,7 +26,7 @@ import (
)
func setupAuthMiddlewareTest(t *testing.T, identity *authn.Identity, authErr error) *contexthandler.ContextHandler {
return contexthandler.ProvideService(setting.NewCfg(), tracing.InitializeTracerForTest(), featuremgmt.WithFeatures(), &authntest.FakeService{
return contexthandler.ProvideService(setting.NewCfg(), tracing.InitializeTracerForTest(), &authntest.FakeService{
ExpectedErr: authErr,
ExpectedIdentity: identity,
})

@ -19,7 +19,6 @@ import (
"github.com/grafana/grafana/pkg/services/authn/authntest"
"github.com/grafana/grafana/pkg/services/contexthandler"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/navtree"
"github.com/grafana/grafana/pkg/services/user/usertest"
"github.com/grafana/grafana/pkg/setting"
@ -273,5 +272,5 @@ func getContextHandler(t *testing.T, cfg *setting.Cfg, authnService authn.Servic
t.Helper()
tracer := tracing.InitializeTracerForTest()
return contexthandler.ProvideService(cfg, tracer, featuremgmt.WithFeatures(), authnService)
return contexthandler.ProvideService(cfg, tracer, authnService)
}

@ -360,6 +360,7 @@ var wireBasicSet = wire.NewSet(
authnimpl.ProvideService,
authnimpl.ProvideIdentitySynchronizer,
authnimpl.ProvideAuthnService,
authnimpl.ProvideAuthnServiceAuthenticateOnly,
authnimpl.ProvideRegistration,
supportbundlesimpl.ProvideService,
extsvcaccounts.ProvideExtSvcAccountsService,

@ -10,6 +10,7 @@ import (
"strings"
"time"
"github.com/grafana/grafana/pkg/web"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/mod/semver"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -53,6 +54,7 @@ func SetupConfig(
buildVersion string,
buildCommit string,
buildBranch string,
optionalMiddlewares ...web.Middleware,
) error {
defsGetter := GetOpenAPIDefinitions(builders)
serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(
@ -98,6 +100,14 @@ func SetupConfig(
handler := filters.WithTracingHTTPLoggingAttributes(requestHandler)
handler = filters.WithRequester(handler)
handler = genericapiserver.DefaultBuildHandlerChain(handler, c)
// If optional middlewares include auth function, they need to happen before DefaultBuildHandlerChain
if len(optionalMiddlewares) > 0 {
for _, m := range optionalMiddlewares {
handler = m(handler)
}
}
handler = filters.WithAcceptHeader(handler)
handler = filters.WithPathRewriters(handler, pathRewriters)
handler = k8stracing.WithTracing(handler, serverConfig.TracerProvider, "KubernetesAPI")

@ -5,6 +5,7 @@ import (
"fmt"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/web"
"github.com/prometheus/client_golang/prometheus"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -33,6 +34,9 @@ type APIServerFactory interface {
// Given the flags, what can we produce
GetEnabled(runtime []RuntimeConfig) ([]schema.GroupVersion, error)
// Any optional middlewares this factory wants configured via apiserver's BuildHandlerChain facility
GetOptionalMiddlewares(tracer tracing.Tracer) []web.Middleware
// Make an API server for a given group+version
MakeAPIServer(ctx context.Context, tracer tracing.Tracer, gv schema.GroupVersion) (builder.APIGroupBuilder, error)
@ -50,6 +54,10 @@ func (p *DummyAPIFactory) GetOptions() options.OptionsProvider {
return nil
}
func (p *DummyAPIFactory) GetOptionalMiddlewares(_ tracing.Tracer) []web.Middleware {
return []web.Middleware{}
}
func (p *DummyAPIFactory) GetEnabled(runtime []RuntimeConfig) ([]schema.GroupVersion, error) {
gv := []schema.GroupVersion{}
for _, cfg := range runtime {

@ -73,9 +73,13 @@ type PostAuthHookFn func(ctx context.Context, identity *Identity, r *Request) er
type PostLoginHookFn func(ctx context.Context, identity *Identity, r *Request, err error)
type PreLogoutHookFn func(ctx context.Context, requester identity.Requester, sessionToken *usertoken.UserToken) error
type Service interface {
type Authenticator interface {
// Authenticate authenticates a request
Authenticate(ctx context.Context, r *Request) (*Identity, error)
}
type Service interface {
Authenticator
// RegisterPostAuthHook registers a hook with a priority that is called after a successful authentication.
// A lower number means higher priority.
RegisterPostAuthHook(hook PostAuthHookFn, priority uint)
@ -115,10 +119,9 @@ type IdentitySynchronizer interface {
}
type Client interface {
Authenticator
// Name returns the name of a client
Name() string
// Authenticate performs the authentication for the request
Authenticate(ctx context.Context, r *Request) (*Identity, error)
// IsEnabled returns the enabled status of the client
IsEnabled() bool
}

@ -39,6 +39,11 @@ func ProvideAuthnService(s *Service) authn.Service {
return s
}
// make sure service also implements authn.ServiceAuthenticateOnly interface
func ProvideAuthnServiceAuthenticateOnly(s *Service) authn.Authenticator {
return s
}
// make sure service implements authn.IdentitySynchronizer interface
func ProvideIdentitySynchronizer(s *Service) authn.IdentitySynchronizer {
return s

@ -20,9 +20,8 @@ import (
var _ authn.Client = new(ExtendedJWT)
const (
extJWTAuthenticationHeaderName = "X-Access-Token"
extJWTAuthorizationHeaderName = "X-Grafana-Id"
extJWTAccessTokenExpectAudience = "grafana"
ExtJWTAuthenticationHeaderName = "X-Access-Token"
ExtJWTAuthorizationHeaderName = "X-Grafana-Id"
)
var (
@ -46,7 +45,7 @@ func ProvideExtendedJWT(cfg *setting.Cfg) *ExtendedJWT {
})
accessTokenVerifier := authlib.NewAccessTokenVerifier(authlib.VerifierConfig{
AllowedAudiences: []string{extJWTAccessTokenExpectAudience},
AllowedAudiences: cfg.ExtJWTAuth.Audiences,
}, keys)
// For ID tokens, we explicitly do not validate audience, hence an empty AllowedAudiences
@ -129,11 +128,19 @@ func (s *ExtendedJWT) authenticateAsUser(
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", userID.String())
}
// For use in service layer, allow higher privilege
allowedKubernetesNamespace := accessTokenClaims.Rest.Namespace
if len(s.cfg.StackID) > 0 {
// For single-tenant cloud use, choose the lower of the two (id token will always have the specific namespace)
allowedKubernetesNamespace = idTokenClaims.Rest.Namespace
}
return &authn.Identity{
ID: userID,
OrgID: s.getDefaultOrgID(),
AuthenticatedBy: login.ExtendedJWTModule,
AuthID: accessID.String(),
ID: userID,
OrgID: s.getDefaultOrgID(),
AuthenticatedBy: login.ExtendedJWTModule,
AuthID: accessID.String(),
AllowedKubernetesNamespace: allowedKubernetesNamespace,
ClientParams: authn.ClientParams{
SyncPermissions: true,
FetchPermissionsParams: authn.FetchPermissionsParams{
@ -159,11 +166,12 @@ func (s *ExtendedJWT) authenticateAsService(claims *authlib.Claims[authlib.Acces
}
return &authn.Identity{
ID: id,
UID: id,
OrgID: s.getDefaultOrgID(),
AuthenticatedBy: login.ExtendedJWTModule,
AuthID: claims.Subject,
ID: id,
UID: id,
OrgID: s.getDefaultOrgID(),
AuthenticatedBy: login.ExtendedJWTModule,
AuthID: claims.Subject,
AllowedKubernetesNamespace: claims.Rest.Namespace,
ClientParams: authn.ClientParams{
SyncPermissions: true,
FetchPermissionsParams: authn.FetchPermissionsParams{
@ -208,7 +216,7 @@ func (s *ExtendedJWT) Priority() uint {
// retrieveAuthenticationToken retrieves the JWT token from the request.
func (s *ExtendedJWT) retrieveAuthenticationToken(httpRequest *http.Request) string {
jwtToken := httpRequest.Header.Get(extJWTAuthenticationHeaderName)
jwtToken := httpRequest.Header.Get(ExtJWTAuthenticationHeaderName)
// Strip the 'Bearer' prefix if it exists.
return strings.TrimPrefix(jwtToken, "Bearer ")
@ -216,7 +224,7 @@ func (s *ExtendedJWT) retrieveAuthenticationToken(httpRequest *http.Request) str
// retrieveAuthorizationToken retrieves the JWT token from the request.
func (s *ExtendedJWT) retrieveAuthorizationToken(httpRequest *http.Request) string {
jwtToken := httpRequest.Header.Get(extJWTAuthorizationHeaderName)
jwtToken := httpRequest.Header.Get(ExtJWTAuthorizationHeaderName)
// Strip the 'Bearer' prefix if it exists.
return strings.TrimPrefix(jwtToken, "Bearer ")

@ -51,6 +51,17 @@ var (
Namespace: "default", // org ID of 1 is special and translates to default
},
}
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: "stack-1234",
},
}
validAcessTokenClaimsWildcard = accessTokenClaims{
Claims: &jwt.Claims{
Subject: "access-policy:this-uid",
@ -183,6 +194,7 @@ func TestExtendedJWT_Test(t *testing.T) {
func TestExtendedJWT_Authenticate(t *testing.T) {
type testCase struct {
name string
cfg *setting.Cfg // optional, only used when overriding the cfg provided by default test setup
accessToken *accessTokenClaims
idToken *idTokenClaims
orgID int64
@ -195,11 +207,12 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
accessToken: &validAccessTokenClaims,
orgID: 1,
want: &authn.Identity{
ID: authn.MustParseNamespaceID("access-policy:this-uid"),
UID: authn.MustParseNamespaceID("access-policy:this-uid"),
OrgID: 1,
AuthenticatedBy: "extendedjwt",
AuthID: "access-policy:this-uid",
ID: authn.MustParseNamespaceID("access-policy:this-uid"),
UID: authn.MustParseNamespaceID("access-policy:this-uid"),
OrgID: 1,
AllowedKubernetesNamespace: "default",
AuthenticatedBy: "extendedjwt",
AuthID: "access-policy:this-uid",
ClientParams: authn.ClientParams{
SyncPermissions: true,
FetchPermissionsParams: authn.FetchPermissionsParams{Roles: []string{"fixed:folders:reader"}}},
@ -210,11 +223,12 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
accessToken: &validAcessTokenClaimsWildcard,
orgID: 1,
want: &authn.Identity{
ID: authn.MustParseNamespaceID("access-policy:this-uid"),
UID: authn.MustParseNamespaceID("access-policy:this-uid"),
OrgID: 1,
AuthenticatedBy: "extendedjwt",
AuthID: "access-policy:this-uid",
ID: authn.MustParseNamespaceID("access-policy:this-uid"),
UID: authn.MustParseNamespaceID("access-policy:this-uid"),
OrgID: 1,
AllowedKubernetesNamespace: "*",
AuthenticatedBy: "extendedjwt",
AuthID: "access-policy:this-uid",
ClientParams: authn.ClientParams{
SyncPermissions: true,
},
@ -226,10 +240,11 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
idToken: &validIDTokenClaims,
orgID: 1,
want: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"),
OrgID: 1,
AuthenticatedBy: "extendedjwt",
AuthID: "access-policy:this-uid",
ID: authn.MustParseNamespaceID("user:2"),
OrgID: 1,
AllowedKubernetesNamespace: "default",
AuthenticatedBy: "extendedjwt",
AuthID: "access-policy:this-uid",
ClientParams: authn.ClientParams{
FetchSyncedUser: true,
SyncPermissions: true,
@ -245,10 +260,36 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
idToken: &validIDTokenClaims,
orgID: 1,
want: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"),
OrgID: 1,
AuthenticatedBy: "extendedjwt",
AuthID: "access-policy:this-uid",
ID: authn.MustParseNamespaceID("user:2"),
OrgID: 1,
AllowedKubernetesNamespace: "*",
AuthenticatedBy: "extendedjwt",
AuthID: "access-policy:this-uid",
ClientParams: authn.ClientParams{
FetchSyncedUser: true,
SyncPermissions: true,
},
},
},
{
name: "should authenticate as user using wildcard namespace for access token, setting allowed namespace to specific",
accessToken: &validAcessTokenClaimsWildcard,
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: authn.MustParseNamespaceID("user:2"),
OrgID: 1,
AllowedKubernetesNamespace: "stack-1234",
AuthenticatedBy: "extendedjwt",
AuthID: "access-policy:this-uid",
ClientParams: authn.ClientParams{
FetchSyncedUser: true,
SyncPermissions: true,
@ -301,7 +342,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
env := setupTestCtx(nil)
env := setupTestCtx(tc.cfg)
validHTTPReq := &http.Request{
Header: map[string][]string{
@ -313,7 +354,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
if tc.idToken != nil {
env.s.accessTokenVerifier = &mockVerifier{Claims: *tc.accessToken}
env.s.idTokenVerifier = &mockIDVerifier{Claims: *tc.idToken}
validHTTPReq.Header.Add(extJWTAuthorizationHeaderName, generateIDToken(*tc.idToken, pk, jose.RS256))
validHTTPReq.Header.Add(ExtJWTAuthorizationHeaderName, generateIDToken(*tc.idToken, pk, jose.RS256))
}
id, err := env.s.Authenticate(context.Background(), &authn.Request{

@ -47,6 +47,8 @@ type Identity struct {
// AuthId is the unique identifier for the entity in the external system.
// Empty if the identity is provided by Grafana.
AuthID string
// AllowedKubernetesNamespace
AllowedKubernetesNamespace string
// IsDisabled is true if the entity is disabled.
IsDisabled bool
// HelpFlags1 is the help flags for the entity.
@ -127,6 +129,10 @@ func (i *Identity) GetLogin() string {
return i.Login
}
func (i *Identity) GetAllowedKubernetesNamespace() string {
return i.AllowedKubernetesNamespace
}
func (i *Identity) GetOrgID() int64 {
return i.OrgID
}

@ -6,6 +6,7 @@ import (
"fmt"
"net/http"
authnClients "github.com/grafana/grafana/pkg/services/authn/clients"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
@ -16,29 +17,26 @@ import (
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
)
func ProvideService(cfg *setting.Cfg, tracer tracing.Tracer, features featuremgmt.FeatureToggles, authnService authn.Service,
func ProvideService(cfg *setting.Cfg, tracer tracing.Tracer, authenticator authn.Authenticator,
) *ContextHandler {
return &ContextHandler{
Cfg: cfg,
tracer: tracer,
features: features,
authnService: authnService,
Cfg: cfg,
tracer: tracer,
authenticator: authenticator,
}
}
// ContextHandler is a middleware.
type ContextHandler struct {
Cfg *setting.Cfg
tracer tracing.Tracer
features featuremgmt.FeatureToggles
authnService authn.Service
Cfg *setting.Cfg
tracer tracing.Tracer
authenticator authn.Authenticator
}
type reqContextKey = ctxkey.Key
@ -112,7 +110,7 @@ func (h *ContextHandler) Middleware(next http.Handler) http.Handler {
reqContext.Logger = reqContext.Logger.New("traceID", traceID)
}
id, err := h.authnService.Authenticate(ctx, &authn.Request{HTTPRequest: reqContext.Req})
id, err := h.authenticator.Authenticate(ctx, &authn.Request{HTTPRequest: reqContext.Req})
if err != nil {
// Hack: set all errors on LookupTokenErr, so we can check it in auth middlewares
reqContext.LookupTokenErr = err
@ -124,6 +122,8 @@ func (h *ContextHandler) Middleware(next http.Handler) http.Handler {
reqContext.IsRenderCall = id.IsAuthenticatedBy(login.RenderModule)
}
h.excludeSensitiveHeadersFromRequest(reqContext.Req)
reqContext.Logger = reqContext.Logger.New("userId", reqContext.UserID, "orgId", reqContext.OrgID, "uname", reqContext.Login)
span.AddEvent("user", trace.WithAttributes(
attribute.String("uname", reqContext.Login),
@ -142,6 +142,11 @@ func (h *ContextHandler) Middleware(next http.Handler) http.Handler {
})
}
func (h *ContextHandler) excludeSensitiveHeadersFromRequest(req *http.Request) {
req.Header.Del(authnClients.ExtJWTAuthenticationHeaderName)
req.Header.Del(authnClients.ExtJWTAuthorizationHeaderName)
}
func (h *ContextHandler) addIDHeaderEndOfRequestFunc(ident identity.Requester) web.BeforeFunc {
return func(w web.ResponseWriter) {
if w.Written() {

@ -15,7 +15,6 @@ import (
"github.com/grafana/grafana/pkg/services/authn/authntest"
"github.com/grafana/grafana/pkg/services/contexthandler"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
@ -27,7 +26,6 @@ func TestContextHandler(t *testing.T) {
handler := contexthandler.ProvideService(
setting.NewCfg(),
tracing.InitializeTracerForTest(),
featuremgmt.WithFeatures(),
&authntest.FakeService{ExpectedErr: errors.New("some error")},
)
@ -49,7 +47,6 @@ func TestContextHandler(t *testing.T) {
handler := contexthandler.ProvideService(
setting.NewCfg(),
tracing.InitializeTracerForTest(),
featuremgmt.WithFeatures(),
&authntest.FakeService{ExpectedIdentity: id},
)
@ -75,7 +72,6 @@ func TestContextHandler(t *testing.T) {
handler := contexthandler.ProvideService(
setting.NewCfg(),
tracing.InitializeTracerForTest(),
featuremgmt.WithFeatures(),
&authntest.FakeService{ExpectedIdentity: identity},
)
@ -97,7 +93,6 @@ func TestContextHandler(t *testing.T) {
handler := contexthandler.ProvideService(
setting.NewCfg(),
tracing.InitializeTracerForTest(),
featuremgmt.WithFeatures(),
&authntest.FakeService{ExpectedIdentity: identity},
)
@ -128,7 +123,6 @@ func TestContextHandler(t *testing.T) {
handler := contexthandler.ProvideService(
cfg,
tracing.InitializeTracerForTest(),
featuremgmt.WithFeatures(),
&authntest.FakeService{ExpectedIdentity: &authn.Identity{}},
)
@ -154,7 +148,6 @@ func TestContextHandler(t *testing.T) {
handler := contexthandler.ProvideService(
cfg,
tracing.InitializeTracerForTest(),
featuremgmt.WithFeatures(),
&authntest.FakeService{ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID(id)}},
)

@ -27,7 +27,9 @@ type SignedInUser struct {
// AuthID will be set if user signed in using external method
AuthID string
// AuthenticatedBy be set if user signed in using external method
AuthenticatedBy string
AuthenticatedBy string
AllowedKubernetesNamespace string
ApiKeyID int64 `xorm:"api_key_id"`
IsServiceAccount bool `xorm:"is_service_account"`
IsGrafanaAdmin bool
@ -89,6 +91,10 @@ func (u *SignedInUser) HasUniqueId() bool {
return u.IsRealUser() || u.IsApiKeyUser() || u.IsServiceAccountUser()
}
func (u *SignedInUser) GetAllowedKubernetesNamespace() string {
return u.AllowedKubernetesNamespace
}
// GetCacheKey returns a unique key for the entity.
// Add an extra prefix to avoid collisions with other caches
func (u *SignedInUser) GetCacheKey() string {

@ -2,6 +2,10 @@ package setting
import "time"
const (
extJWTAccessTokenExpectAudience = "grafana"
)
type AuthJWTSettings struct {
// JWT Auth
Enabled bool
@ -29,6 +33,7 @@ type ExtJWTSettings struct {
Enabled bool
ExpectIssuer string
JWKSUrl string
Audiences []string
}
func (cfg *Cfg) readAuthExtJWTSettings() {
@ -36,6 +41,9 @@ func (cfg *Cfg) readAuthExtJWTSettings() {
jwtSettings := ExtJWTSettings{}
jwtSettings.Enabled = authExtendedJWT.Key("enabled").MustBool(false)
jwtSettings.JWKSUrl = authExtendedJWT.Key("jwks_url").MustString("")
// for Grafana, this is hard coded, but we leave it as a configurable param for other use-cases
jwtSettings.Audiences = []string{extJWTAccessTokenExpectAudience}
cfg.ExtJWTAuth = jwtSettings
}

@ -139,6 +139,17 @@ func mwFromHandler(handler Handler) Middleware {
}
}
// a convenience function that is provided for users of contexthandler package (standalone apiservers)
// who have an implicit dependency on Macron in context but don't want to take a dependency on
// router additionally
func EmptyMacronMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
m := New()
c := m.createContext(writer, request)
next.ServeHTTP(writer, c.Req) // since c.Req has the newer context attached
})
}
func (m *Macaron) createContext(rw http.ResponseWriter, req *http.Request) *Context {
// NOTE: we have to explicitly copy the middleware chain here to avoid
// passing a shared slice to the *Context, which leads to racy behavior in

Loading…
Cancel
Save