123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- /*
- Copyright 2014 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package authenticator
- import (
- "time"
- "github.com/go-openapi/spec"
- "k8s.io/apiserver/pkg/authentication/authenticator"
- "k8s.io/apiserver/pkg/authentication/authenticatorfactory"
- "k8s.io/apiserver/pkg/authentication/group"
- "k8s.io/apiserver/pkg/authentication/request/anonymous"
- "k8s.io/apiserver/pkg/authentication/request/bearertoken"
- "k8s.io/apiserver/pkg/authentication/request/headerrequest"
- "k8s.io/apiserver/pkg/authentication/request/union"
- "k8s.io/apiserver/pkg/authentication/request/websocket"
- "k8s.io/apiserver/pkg/authentication/request/x509"
- tokencache "k8s.io/apiserver/pkg/authentication/token/cache"
- "k8s.io/apiserver/pkg/authentication/token/tokenfile"
- tokenunion "k8s.io/apiserver/pkg/authentication/token/union"
- "k8s.io/apiserver/pkg/server/dynamiccertificates"
- utilfeature "k8s.io/apiserver/pkg/util/feature"
- "k8s.io/apiserver/plugin/pkg/authenticator/password/passwordfile"
- "k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth"
- "k8s.io/apiserver/plugin/pkg/authenticator/token/oidc"
- "k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
- // Initialize all known client auth plugins.
- _ "k8s.io/client-go/plugin/pkg/client/auth"
- "k8s.io/client-go/util/keyutil"
- "k8s.io/kubernetes/pkg/features"
- "k8s.io/kubernetes/pkg/serviceaccount"
- )
- // Config contains the data on how to authenticate a request to the Kube API Server
- type Config struct {
- Anonymous bool
- BasicAuthFile string
- BootstrapToken bool
- TokenAuthFile string
- OIDCIssuerURL string
- OIDCClientID string
- OIDCCAFile string
- OIDCUsernameClaim string
- OIDCUsernamePrefix string
- OIDCGroupsClaim string
- OIDCGroupsPrefix string
- OIDCSigningAlgs []string
- OIDCRequiredClaims map[string]string
- ServiceAccountKeyFiles []string
- ServiceAccountLookup bool
- ServiceAccountIssuer string
- APIAudiences authenticator.Audiences
- WebhookTokenAuthnConfigFile string
- WebhookTokenAuthnVersion string
- WebhookTokenAuthnCacheTTL time.Duration
- TokenSuccessCacheTTL time.Duration
- TokenFailureCacheTTL time.Duration
- RequestHeaderConfig *authenticatorfactory.RequestHeaderConfig
- // TODO, this is the only non-serializable part of the entire config. Factor it out into a clientconfig
- ServiceAccountTokenGetter serviceaccount.ServiceAccountTokenGetter
- BootstrapTokenAuthenticator authenticator.Token
- // ClientCAContentProvider are the options for verifying incoming connections using mTLS and directly assigning to users.
- // Generally this is the CA bundle file used to authenticate client certificates
- // If this value is nil, then mutual TLS is disabled.
- ClientCAContentProvider dynamiccertificates.CAContentProvider
- }
- // New returns an authenticator.Request or an error that supports the standard
- // Kubernetes authentication mechanisms.
- func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, error) {
- var authenticators []authenticator.Request
- var tokenAuthenticators []authenticator.Token
- securityDefinitions := spec.SecurityDefinitions{}
- // front-proxy, BasicAuth methods, local first, then remote
- // Add the front proxy authenticator if requested
- if config.RequestHeaderConfig != nil {
- requestHeaderAuthenticator := headerrequest.NewDynamicVerifyOptionsSecure(
- config.RequestHeaderConfig.CAContentProvider.VerifyOptions,
- config.RequestHeaderConfig.AllowedClientNames,
- config.RequestHeaderConfig.UsernameHeaders,
- config.RequestHeaderConfig.GroupHeaders,
- config.RequestHeaderConfig.ExtraHeaderPrefixes,
- )
- authenticators = append(authenticators, authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, requestHeaderAuthenticator))
- }
- // basic auth
- if len(config.BasicAuthFile) > 0 {
- basicAuth, err := newAuthenticatorFromBasicAuthFile(config.BasicAuthFile)
- if err != nil {
- return nil, nil, err
- }
- authenticators = append(authenticators, authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, basicAuth))
- securityDefinitions["HTTPBasic"] = &spec.SecurityScheme{
- SecuritySchemeProps: spec.SecuritySchemeProps{
- Type: "basic",
- Description: "HTTP Basic authentication",
- },
- }
- }
- // X509 methods
- if config.ClientCAContentProvider != nil {
- certAuth := x509.NewDynamic(config.ClientCAContentProvider.VerifyOptions, x509.CommonNameUserConversion)
- authenticators = append(authenticators, certAuth)
- }
- // Bearer token methods, local first, then remote
- if len(config.TokenAuthFile) > 0 {
- tokenAuth, err := newAuthenticatorFromTokenFile(config.TokenAuthFile)
- if err != nil {
- return nil, nil, err
- }
- tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, tokenAuth))
- }
- if len(config.ServiceAccountKeyFiles) > 0 {
- serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.APIAudiences, config.ServiceAccountTokenGetter)
- if err != nil {
- return nil, nil, err
- }
- tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
- }
- if utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) && config.ServiceAccountIssuer != "" {
- serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuer, config.ServiceAccountKeyFiles, config.APIAudiences, config.ServiceAccountTokenGetter)
- if err != nil {
- return nil, nil, err
- }
- tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
- }
- if config.BootstrapToken {
- if config.BootstrapTokenAuthenticator != nil {
- // TODO: This can sometimes be nil because of
- tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, config.BootstrapTokenAuthenticator))
- }
- }
- // NOTE(ericchiang): Keep the OpenID Connect after Service Accounts.
- //
- // Because both plugins verify JWTs whichever comes first in the union experiences
- // cache misses for all requests using the other. While the service account plugin
- // simply returns an error, the OpenID Connect plugin may query the provider to
- // update the keys, causing performance hits.
- if len(config.OIDCIssuerURL) > 0 && len(config.OIDCClientID) > 0 {
- oidcAuth, err := newAuthenticatorFromOIDCIssuerURL(oidc.Options{
- IssuerURL: config.OIDCIssuerURL,
- ClientID: config.OIDCClientID,
- CAFile: config.OIDCCAFile,
- UsernameClaim: config.OIDCUsernameClaim,
- UsernamePrefix: config.OIDCUsernamePrefix,
- GroupsClaim: config.OIDCGroupsClaim,
- GroupsPrefix: config.OIDCGroupsPrefix,
- SupportedSigningAlgs: config.OIDCSigningAlgs,
- RequiredClaims: config.OIDCRequiredClaims,
- })
- if err != nil {
- return nil, nil, err
- }
- tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, oidcAuth))
- }
- if len(config.WebhookTokenAuthnConfigFile) > 0 {
- webhookTokenAuth, err := newWebhookTokenAuthenticator(config.WebhookTokenAuthnConfigFile, config.WebhookTokenAuthnVersion, config.WebhookTokenAuthnCacheTTL, config.APIAudiences)
- if err != nil {
- return nil, nil, err
- }
- tokenAuthenticators = append(tokenAuthenticators, webhookTokenAuth)
- }
- if len(tokenAuthenticators) > 0 {
- // Union the token authenticators
- tokenAuth := tokenunion.New(tokenAuthenticators...)
- // Optionally cache authentication results
- if config.TokenSuccessCacheTTL > 0 || config.TokenFailureCacheTTL > 0 {
- tokenAuth = tokencache.New(tokenAuth, true, config.TokenSuccessCacheTTL, config.TokenFailureCacheTTL)
- }
- authenticators = append(authenticators, bearertoken.New(tokenAuth), websocket.NewProtocolAuthenticator(tokenAuth))
- securityDefinitions["BearerToken"] = &spec.SecurityScheme{
- SecuritySchemeProps: spec.SecuritySchemeProps{
- Type: "apiKey",
- Name: "authorization",
- In: "header",
- Description: "Bearer Token authentication",
- },
- }
- }
- if len(authenticators) == 0 {
- if config.Anonymous {
- return anonymous.NewAuthenticator(), &securityDefinitions, nil
- }
- return nil, &securityDefinitions, nil
- }
- authenticator := union.New(authenticators...)
- authenticator = group.NewAuthenticatedGroupAdder(authenticator)
- if config.Anonymous {
- // If the authenticator chain returns an error, return an error (don't consider a bad bearer token
- // or invalid username/password combination anonymous).
- authenticator = union.NewFailOnError(authenticator, anonymous.NewAuthenticator())
- }
- return authenticator, &securityDefinitions, nil
- }
- // IsValidServiceAccountKeyFile returns true if a valid public RSA key can be read from the given file
- func IsValidServiceAccountKeyFile(file string) bool {
- _, err := keyutil.PublicKeysFromFile(file)
- return err == nil
- }
- // newAuthenticatorFromBasicAuthFile returns an authenticator.Request or an error
- func newAuthenticatorFromBasicAuthFile(basicAuthFile string) (authenticator.Request, error) {
- basicAuthenticator, err := passwordfile.NewCSV(basicAuthFile)
- if err != nil {
- return nil, err
- }
- return basicauth.New(basicAuthenticator), nil
- }
- // newAuthenticatorFromTokenFile returns an authenticator.Token or an error
- func newAuthenticatorFromTokenFile(tokenAuthFile string) (authenticator.Token, error) {
- tokenAuthenticator, err := tokenfile.NewCSV(tokenAuthFile)
- if err != nil {
- return nil, err
- }
- return tokenAuthenticator, nil
- }
- // newAuthenticatorFromOIDCIssuerURL returns an authenticator.Token or an error.
- func newAuthenticatorFromOIDCIssuerURL(opts oidc.Options) (authenticator.Token, error) {
- const noUsernamePrefix = "-"
- if opts.UsernamePrefix == "" && opts.UsernameClaim != "email" {
- // Old behavior. If a usernamePrefix isn't provided, prefix all claims other than "email"
- // with the issuerURL.
- //
- // See https://github.com/kubernetes/kubernetes/issues/31380
- opts.UsernamePrefix = opts.IssuerURL + "#"
- }
- if opts.UsernamePrefix == noUsernamePrefix {
- // Special value indicating usernames shouldn't be prefixed.
- opts.UsernamePrefix = ""
- }
- tokenAuthenticator, err := oidc.New(opts)
- if err != nil {
- return nil, err
- }
- return tokenAuthenticator, nil
- }
- // newLegacyServiceAccountAuthenticator returns an authenticator.Token or an error
- func newLegacyServiceAccountAuthenticator(keyfiles []string, lookup bool, apiAudiences authenticator.Audiences, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
- allPublicKeys := []interface{}{}
- for _, keyfile := range keyfiles {
- publicKeys, err := keyutil.PublicKeysFromFile(keyfile)
- if err != nil {
- return nil, err
- }
- allPublicKeys = append(allPublicKeys, publicKeys...)
- }
- tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(serviceaccount.LegacyIssuer, allPublicKeys, apiAudiences, serviceaccount.NewLegacyValidator(lookup, serviceAccountGetter))
- return tokenAuthenticator, nil
- }
- // newServiceAccountAuthenticator returns an authenticator.Token or an error
- func newServiceAccountAuthenticator(iss string, keyfiles []string, apiAudiences authenticator.Audiences, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
- allPublicKeys := []interface{}{}
- for _, keyfile := range keyfiles {
- publicKeys, err := keyutil.PublicKeysFromFile(keyfile)
- if err != nil {
- return nil, err
- }
- allPublicKeys = append(allPublicKeys, publicKeys...)
- }
- tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(iss, allPublicKeys, apiAudiences, serviceaccount.NewValidator(serviceAccountGetter))
- return tokenAuthenticator, nil
- }
- func newWebhookTokenAuthenticator(webhookConfigFile string, version string, ttl time.Duration, implicitAuds authenticator.Audiences) (authenticator.Token, error) {
- webhookTokenAuthenticator, err := webhook.New(webhookConfigFile, version, implicitAuds)
- if err != nil {
- return nil, err
- }
- return tokencache.New(webhookTokenAuthenticator, false, ttl, ttl), nil
- }
|