mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
K8s: standalone authenticator that allows a type of downstream forwarding (#85130)
This commit is contained in:
@@ -11,10 +11,14 @@ aggregation path altogether and just run this example apiserver as a standalone
|
||||
|
||||
### Usage
|
||||
|
||||
For setting `--grafana.authn.signing-keys-url`, Grafana must be run with `idForwarding = true` while also ensuring
|
||||
you have logged in to the instance at least once.
|
||||
|
||||
```shell
|
||||
go run ./pkg/cmd/grafana apiserver \
|
||||
--runtime-config=example.grafana.app/v0alpha1=true \
|
||||
--grafana-apiserver-dev-mode \
|
||||
--grafana.authn.signing-keys-url="http://localhost:3000/api/signing-keys/keys" \
|
||||
--verbosity 10 \
|
||||
--secure-port 7443
|
||||
```
|
||||
|
||||
71
pkg/cmd/grafana/apiserver/auth/tokenAuthenticator.go
Normal file
71
pkg/cmd/grafana/apiserver/auth/tokenAuthenticator.go
Normal file
@@ -0,0 +1,71 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
57
pkg/cmd/grafana/apiserver/auth/util.go
Normal file
57
pkg/cmd/grafana/apiserver/auth/util.go
Normal file
@@ -0,0 +1,57 @@
|
||||
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
|
||||
}
|
||||
24
pkg/cmd/grafana/apiserver/auth/validator.go
Normal file
24
pkg/cmd/grafana/apiserver/auth/validator.go
Normal file
@@ -0,0 +1,24 @@
|
||||
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
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net"
|
||||
"path"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
netutils "k8s.io/utils/net"
|
||||
|
||||
"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/tracing"
|
||||
grafanaAPIServer "github.com/grafana/grafana/pkg/services/apiserver"
|
||||
@@ -20,12 +22,10 @@ import (
|
||||
standaloneoptions "github.com/grafana/grafana/pkg/services/apiserver/standalone/options"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/utils"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultEtcdPathPrefix = "/registry/grafana.app"
|
||||
dataPath = "data/grafana-apiserver" // same as grafana core
|
||||
dataPath = "data/grafana-apiserver" // same as grafana core
|
||||
)
|
||||
|
||||
// APIServerOptions contains the state for the apiserver
|
||||
@@ -101,6 +101,14 @@ func (o *APIServerOptions) Config() (*genericapiserver.RecommendedConfig, error)
|
||||
return nil, fmt.Errorf("failed to apply options to server config: %w", err)
|
||||
}
|
||||
|
||||
// When the ID signing key exists, configure access-token support
|
||||
if len(o.Options.AuthnOptions.IDVerifierConfig.SigningKeysURL) > 0 {
|
||||
serverConfig.Authentication.Authenticator = auth.AppendToAuthenticators(
|
||||
auth.NewAccessTokenAuthenticator(o.Options.AuthnOptions.IDVerifierConfig),
|
||||
serverConfig.Authentication.Authenticator,
|
||||
)
|
||||
}
|
||||
|
||||
serverConfig.DisabledPostStartHooks = serverConfig.DisabledPostStartHooks.Insert("generic-apiserver-start-informers")
|
||||
serverConfig.DisabledPostStartHooks = serverConfig.DisabledPostStartHooks.Insert("priority-and-fairness-config-consumer")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user