mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
K8s: remove standalone authenticator in favor of providing one through the factory (#85901)
This commit is contained in:
@@ -180,7 +180,7 @@ If it's not perfectly clear that it's an actual bug, quickly try to reproduce it
|
|||||||
|
|
||||||
1. Add a comment describing detailed steps for how to reproduce it, if applicable.
|
1. Add a comment describing detailed steps for how to reproduce it, if applicable.
|
||||||
2. Label the issue `type/bug` and at least one `area/*` or `datasource/*` label.
|
2. Label the issue `type/bug` and at least one `area/*` or `datasource/*` label.
|
||||||
3. If you know that maintainers wont be able to put any resources into it for some time then label the issue with `help wanted` and optionally `beginner friendly` together with pointers on which code to update to fix the bug. This should signal to the community that we would appreciate any help we can get to resolve this.
|
3. If you know that maintainers won't be able to put any resources into it for some time then label the issue with `help wanted` and optionally `beginner friendly` together with pointers on which code to update to fix the bug. This should signal to the community that we would appreciate any help we can get to resolve this.
|
||||||
4. Move on to [prioritizing the issue](#4-prioritization-of-issues).
|
4. Move on to [prioritizing the issue](#4-prioritization-of-issues).
|
||||||
|
|
||||||
**It can't be reproduced:**
|
**It can't be reproduced:**
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
package auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/grafana/authlib/authn"
|
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
headerKeyAccessToken = "X-Access-Token"
|
|
||||||
headerKeyGrafanaID = "X-Grafana-Id"
|
|
||||||
|
|
||||||
extraKeyAccessToken = "access-token"
|
|
||||||
extraKeyGrafanaID = "id-token"
|
|
||||||
extraKeyGLSA = "glsa"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewAccessTokenAuthenticator(config *authn.IDVerifierConfig) authenticator.RequestFunc {
|
|
||||||
verifier := authn.NewVerifier[CustomClaims](authn.IDVerifierConfig{
|
|
||||||
SigningKeysURL: config.SigningKeysURL,
|
|
||||||
AllowedAudiences: config.AllowedAudiences,
|
|
||||||
})
|
|
||||||
return getAccessTokenAuthenticatorFunc(&TokenValidator{verifier})
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAccessTokenAuthenticatorFunc(validator *TokenValidator) authenticator.RequestFunc {
|
|
||||||
return func(req *http.Request) (*authenticator.Response, bool, error) {
|
|
||||||
accessToken := req.Header.Get(headerKeyAccessToken)
|
|
||||||
if accessToken == "" {
|
|
||||||
return nil, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// While the authn token system is in development, we can temporarily use
|
|
||||||
// service account tokens. Note this does not grant any real permissions/verification,
|
|
||||||
// it simply allows forwarding the token to the next request
|
|
||||||
if strings.HasPrefix(accessToken, "glsa_") {
|
|
||||||
return &authenticator.Response{
|
|
||||||
Audiences: authenticator.Audiences([]string{}),
|
|
||||||
User: &user.DefaultInfo{
|
|
||||||
Name: "glsa-forwarding-request",
|
|
||||||
UID: "",
|
|
||||||
Groups: []string{},
|
|
||||||
Extra: map[string][]string{
|
|
||||||
extraKeyGLSA: {accessToken},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := validator.Validate(req.Context(), accessToken)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &authenticator.Response{
|
|
||||||
Audiences: authenticator.Audiences(result.Claims.Audience),
|
|
||||||
User: &user.DefaultInfo{
|
|
||||||
Name: result.Subject,
|
|
||||||
UID: "",
|
|
||||||
Groups: []string{},
|
|
||||||
Extra: map[string][]string{
|
|
||||||
extraKeyAccessToken: {accessToken},
|
|
||||||
extraKeyGrafanaID: {req.Header.Get("X-Grafana-Id")}, // this may exist if starting with a user
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
|
||||||
"k8s.io/apiserver/pkg/authentication/request/union"
|
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
|
||||||
)
|
|
||||||
|
|
||||||
func AppendToAuthenticators(newAuthenticator authenticator.RequestFunc, authRequestHandlers ...authenticator.Request) authenticator.Request {
|
|
||||||
handlers := append([]authenticator.Request{newAuthenticator}, authRequestHandlers...)
|
|
||||||
return union.New(handlers...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get tokens that can be forwarded to the next service
|
|
||||||
// In the future this will need to create new tokens with a new audience
|
|
||||||
func GetIDForwardingAuthHeaders(ctx context.Context) (map[string]string, error) {
|
|
||||||
user, ok := request.UserFrom(ctx)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("missing user")
|
|
||||||
}
|
|
||||||
|
|
||||||
getter := func(key string) string {
|
|
||||||
vals, ok := user.GetExtra()[key]
|
|
||||||
if ok && len(vals) == 1 {
|
|
||||||
return vals[0]
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
token := getter(extraKeyGLSA)
|
|
||||||
if token != "" {
|
|
||||||
// Service account tokens get forwarded as auth tokens
|
|
||||||
// this lets us keep testing the workflows while the ID token system is in dev
|
|
||||||
return map[string]string{
|
|
||||||
"Authorization": "Bearer " + token,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
accessToken := getter(extraKeyAccessToken)
|
|
||||||
if accessToken == "" {
|
|
||||||
return nil, fmt.Errorf("missing access token in user info")
|
|
||||||
}
|
|
||||||
|
|
||||||
idToken := getter(extraKeyGrafanaID)
|
|
||||||
if idToken != "" {
|
|
||||||
return map[string]string{
|
|
||||||
headerKeyAccessToken: accessToken,
|
|
||||||
headerKeyGrafanaID: idToken,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
return map[string]string{
|
|
||||||
headerKeyAccessToken: accessToken,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/grafana/authlib/authn"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CustomClaims struct {
|
|
||||||
// Nothing yet
|
|
||||||
}
|
|
||||||
|
|
||||||
type TokenValidator struct {
|
|
||||||
verifier authn.Verifier[CustomClaims]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *TokenValidator) Validate(ctx context.Context, token string) (*authn.Claims[CustomClaims], error) {
|
|
||||||
customClaims, err := v.verifier.Verify(ctx, token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return customClaims, nil
|
|
||||||
}
|
|
||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
netutils "k8s.io/utils/net"
|
netutils "k8s.io/utils/net"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apiserver/builder"
|
"github.com/grafana/grafana/pkg/apiserver/builder"
|
||||||
"github.com/grafana/grafana/pkg/cmd/grafana/apiserver/auth"
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||||
grafanaAPIServer "github.com/grafana/grafana/pkg/services/apiserver"
|
grafanaAPIServer "github.com/grafana/grafana/pkg/services/apiserver"
|
||||||
@@ -101,12 +100,11 @@ func (o *APIServerOptions) Config() (*genericapiserver.RecommendedConfig, error)
|
|||||||
return nil, fmt.Errorf("failed to apply options to server config: %w", err)
|
return nil, fmt.Errorf("failed to apply options to server config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// When the ID signing key exists, configure access-token support
|
if factoryOptions := o.factory.GetOptions(); factoryOptions != nil {
|
||||||
if len(o.Options.AuthnOptions.IDVerifierConfig.SigningKeysURL) > 0 {
|
err := factoryOptions.ApplyTo(serverConfig)
|
||||||
serverConfig.Authentication.Authenticator = auth.AppendToAuthenticators(
|
if err != nil {
|
||||||
auth.NewAccessTokenAuthenticator(o.Options.AuthnOptions.IDVerifierConfig),
|
return nil, fmt.Errorf("factory's applyTo func failed: %s", err.Error())
|
||||||
serverConfig.Authentication.Authenticator,
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
serverConfig.DisabledPostStartHooks = serverConfig.DisabledPostStartHooks.Insert("generic-apiserver-start-informers")
|
serverConfig.DisabledPostStartHooks = serverConfig.DisabledPostStartHooks.Insert("generic-apiserver-start-informers")
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
type OptionsProvider interface {
|
type OptionsProvider interface {
|
||||||
AddFlags(fs *pflag.FlagSet)
|
AddFlags(fs *pflag.FlagSet)
|
||||||
|
ApplyTo(config *genericapiserver.RecommendedConfig) error
|
||||||
ValidateOptions() []error
|
ValidateOptions() []error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
|
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
|
||||||
"github.com/grafana/grafana/pkg/apiserver/builder"
|
"github.com/grafana/grafana/pkg/apiserver/builder"
|
||||||
@@ -62,6 +63,10 @@ func (p *DummyAPIFactory) GetEnabled(runtime []RuntimeConfig) ([]schema.GroupVer
|
|||||||
return gv, nil
|
return gv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *DummyAPIFactory) ApplyTo(config *genericapiserver.RecommendedConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *DummyAPIFactory) MakeAPIServer(tracer tracing.Tracer, gv schema.GroupVersion) (builder.APIGroupBuilder, error) {
|
func (p *DummyAPIFactory) MakeAPIServer(tracer tracing.Tracer, gv schema.GroupVersion) (builder.APIGroupBuilder, error) {
|
||||||
if gv.Version != "v0alpha1" {
|
if gv.Version != "v0alpha1" {
|
||||||
return nil, fmt.Errorf("only alpha supported now")
|
return nil, fmt.Errorf("only alpha supported now")
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
package options
|
|
||||||
|
|
||||||
import "github.com/grafana/authlib/authn"
|
|
||||||
|
|
||||||
func NewAuthnOptions() *AuthnOptions {
|
|
||||||
return &AuthnOptions{
|
|
||||||
IDVerifierConfig: &authn.IDVerifierConfig{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,8 +7,6 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
genericoptions "k8s.io/apiserver/pkg/server/options"
|
genericoptions "k8s.io/apiserver/pkg/server/options"
|
||||||
|
|
||||||
"github.com/grafana/authlib/authn"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
@@ -17,7 +15,6 @@ type Options struct {
|
|||||||
RecommendedOptions *genericoptions.RecommendedOptions
|
RecommendedOptions *genericoptions.RecommendedOptions
|
||||||
TracingOptions *TracingOptions
|
TracingOptions *TracingOptions
|
||||||
MetricsOptions *MetricsOptions
|
MetricsOptions *MetricsOptions
|
||||||
AuthnOptions *AuthnOptions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(logger log.Logger, codec runtime.Codec) *Options {
|
func New(logger log.Logger, codec runtime.Codec) *Options {
|
||||||
@@ -27,7 +24,6 @@ func New(logger log.Logger, codec runtime.Codec) *Options {
|
|||||||
RecommendedOptions: options.NewRecommendedOptions(codec),
|
RecommendedOptions: options.NewRecommendedOptions(codec),
|
||||||
TracingOptions: NewTracingOptions(logger),
|
TracingOptions: NewTracingOptions(logger),
|
||||||
MetricsOptions: NewMetrcicsOptions(logger),
|
MetricsOptions: NewMetrcicsOptions(logger),
|
||||||
AuthnOptions: NewAuthnOptions(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +33,6 @@ func (o *Options) AddFlags(fs *pflag.FlagSet) {
|
|||||||
o.RecommendedOptions.AddFlags(fs)
|
o.RecommendedOptions.AddFlags(fs)
|
||||||
o.TracingOptions.AddFlags(fs)
|
o.TracingOptions.AddFlags(fs)
|
||||||
o.MetricsOptions.AddFlags(fs)
|
o.MetricsOptions.AddFlags(fs)
|
||||||
o.AuthnOptions.AddFlags(fs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Options) Validate() []error {
|
func (o *Options) Validate() []error {
|
||||||
@@ -162,15 +157,3 @@ func (o *Options) ApplyTo(serverConfig *genericapiserver.RecommendedConfig) erro
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthnOptions struct {
|
|
||||||
IDVerifierConfig *authn.IDVerifierConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (authOpts *AuthnOptions) AddFlags(fs *pflag.FlagSet) {
|
|
||||||
prefix := "grafana.authn"
|
|
||||||
fs.StringVar(&authOpts.IDVerifierConfig.SigningKeysURL, prefix+".signing-keys-url", "", "URL to jwks endpoint")
|
|
||||||
|
|
||||||
audience := fs.StringSlice(prefix+".allowed-audiences", []string{}, "Specifies a comma-separated list of allowed audiences.")
|
|
||||||
authOpts.IDVerifierConfig.AllowedAudiences = *audience
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user