mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-11-04 07:49:35 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			316 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			316 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
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
 | 
						|
	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,
 | 
						|
			APIAudiences:         config.APIAudiences,
 | 
						|
			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, oidcAuth)
 | 
						|
	}
 | 
						|
	if len(config.WebhookTokenAuthnConfigFile) > 0 {
 | 
						|
		webhookTokenAuth, err := newWebhookTokenAuthenticator(config.WebhookTokenAuthnConfigFile, 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, ttl time.Duration, implicitAuds authenticator.Audiences) (authenticator.Token, error) {
 | 
						|
	webhookTokenAuthenticator, err := webhook.New(webhookConfigFile, implicitAuds)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return tokencache.New(webhookTokenAuthenticator, false, ttl, ttl), nil
 | 
						|
}
 |