k8s.io/kubernetes@v1.29.3/pkg/kubeapiserver/authenticator/config.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package authenticator
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"time"
    23  
    24  	utilnet "k8s.io/apimachinery/pkg/util/net"
    25  	"k8s.io/apimachinery/pkg/util/wait"
    26  	"k8s.io/apiserver/pkg/apis/apiserver"
    27  	"k8s.io/apiserver/pkg/authentication/authenticator"
    28  	"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
    29  	"k8s.io/apiserver/pkg/authentication/group"
    30  	"k8s.io/apiserver/pkg/authentication/request/anonymous"
    31  	"k8s.io/apiserver/pkg/authentication/request/bearertoken"
    32  	"k8s.io/apiserver/pkg/authentication/request/headerrequest"
    33  	"k8s.io/apiserver/pkg/authentication/request/union"
    34  	"k8s.io/apiserver/pkg/authentication/request/websocket"
    35  	"k8s.io/apiserver/pkg/authentication/request/x509"
    36  	tokencache "k8s.io/apiserver/pkg/authentication/token/cache"
    37  	"k8s.io/apiserver/pkg/authentication/token/tokenfile"
    38  	tokenunion "k8s.io/apiserver/pkg/authentication/token/union"
    39  	"k8s.io/apiserver/pkg/server/dynamiccertificates"
    40  	webhookutil "k8s.io/apiserver/pkg/util/webhook"
    41  	"k8s.io/apiserver/plugin/pkg/authenticator/token/oidc"
    42  	"k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
    43  	typedv1core "k8s.io/client-go/kubernetes/typed/core/v1"
    44  	"k8s.io/kube-openapi/pkg/spec3"
    45  	"k8s.io/kube-openapi/pkg/validation/spec"
    46  
    47  	// Initialize all known client auth plugins.
    48  	_ "k8s.io/client-go/plugin/pkg/client/auth"
    49  	"k8s.io/client-go/util/keyutil"
    50  	"k8s.io/kubernetes/pkg/serviceaccount"
    51  )
    52  
    53  // Config contains the data on how to authenticate a request to the Kube API Server
    54  type Config struct {
    55  	Anonymous      bool
    56  	BootstrapToken bool
    57  
    58  	TokenAuthFile               string
    59  	AuthenticationConfig        *apiserver.AuthenticationConfiguration
    60  	OIDCSigningAlgs             []string
    61  	ServiceAccountKeyFiles      []string
    62  	ServiceAccountLookup        bool
    63  	ServiceAccountIssuers       []string
    64  	APIAudiences                authenticator.Audiences
    65  	WebhookTokenAuthnConfigFile string
    66  	WebhookTokenAuthnVersion    string
    67  	WebhookTokenAuthnCacheTTL   time.Duration
    68  	// WebhookRetryBackoff specifies the backoff parameters for the authentication webhook retry logic.
    69  	// This allows us to configure the sleep time at each iteration and the maximum number of retries allowed
    70  	// before we fail the webhook call in order to limit the fan out that ensues when the system is degraded.
    71  	WebhookRetryBackoff *wait.Backoff
    72  
    73  	TokenSuccessCacheTTL time.Duration
    74  	TokenFailureCacheTTL time.Duration
    75  
    76  	RequestHeaderConfig *authenticatorfactory.RequestHeaderConfig
    77  
    78  	// TODO, this is the only non-serializable part of the entire config.  Factor it out into a clientconfig
    79  	ServiceAccountTokenGetter   serviceaccount.ServiceAccountTokenGetter
    80  	SecretsWriter               typedv1core.SecretsGetter
    81  	BootstrapTokenAuthenticator authenticator.Token
    82  	// ClientCAContentProvider are the options for verifying incoming connections using mTLS and directly assigning to users.
    83  	// Generally this is the CA bundle file used to authenticate client certificates
    84  	// If this value is nil, then mutual TLS is disabled.
    85  	ClientCAContentProvider dynamiccertificates.CAContentProvider
    86  
    87  	// Optional field, custom dial function used to connect to webhook
    88  	CustomDial utilnet.DialFunc
    89  }
    90  
    91  // New returns an authenticator.Request or an error that supports the standard
    92  // Kubernetes authentication mechanisms.
    93  func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, spec3.SecuritySchemes, error) {
    94  	var authenticators []authenticator.Request
    95  	var tokenAuthenticators []authenticator.Token
    96  	securityDefinitionsV2 := spec.SecurityDefinitions{}
    97  	securitySchemesV3 := spec3.SecuritySchemes{}
    98  
    99  	// front-proxy, BasicAuth methods, local first, then remote
   100  	// Add the front proxy authenticator if requested
   101  	if config.RequestHeaderConfig != nil {
   102  		requestHeaderAuthenticator := headerrequest.NewDynamicVerifyOptionsSecure(
   103  			config.RequestHeaderConfig.CAContentProvider.VerifyOptions,
   104  			config.RequestHeaderConfig.AllowedClientNames,
   105  			config.RequestHeaderConfig.UsernameHeaders,
   106  			config.RequestHeaderConfig.GroupHeaders,
   107  			config.RequestHeaderConfig.ExtraHeaderPrefixes,
   108  		)
   109  		authenticators = append(authenticators, authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, requestHeaderAuthenticator))
   110  	}
   111  
   112  	// X509 methods
   113  	if config.ClientCAContentProvider != nil {
   114  		certAuth := x509.NewDynamic(config.ClientCAContentProvider.VerifyOptions, x509.CommonNameUserConversion)
   115  		authenticators = append(authenticators, certAuth)
   116  	}
   117  
   118  	// Bearer token methods, local first, then remote
   119  	if len(config.TokenAuthFile) > 0 {
   120  		tokenAuth, err := newAuthenticatorFromTokenFile(config.TokenAuthFile)
   121  		if err != nil {
   122  			return nil, nil, nil, err
   123  		}
   124  		tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, tokenAuth))
   125  	}
   126  	if len(config.ServiceAccountKeyFiles) > 0 {
   127  		serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.APIAudiences, config.ServiceAccountTokenGetter, config.SecretsWriter)
   128  		if err != nil {
   129  			return nil, nil, nil, err
   130  		}
   131  		tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
   132  	}
   133  	if len(config.ServiceAccountIssuers) > 0 {
   134  		serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuers, config.ServiceAccountKeyFiles, config.APIAudiences, config.ServiceAccountTokenGetter)
   135  		if err != nil {
   136  			return nil, nil, nil, err
   137  		}
   138  		tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
   139  	}
   140  
   141  	if config.BootstrapToken && config.BootstrapTokenAuthenticator != nil {
   142  		tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, config.BootstrapTokenAuthenticator))
   143  	}
   144  
   145  	// NOTE(ericchiang): Keep the OpenID Connect after Service Accounts.
   146  	//
   147  	// Because both plugins verify JWTs whichever comes first in the union experiences
   148  	// cache misses for all requests using the other. While the service account plugin
   149  	// simply returns an error, the OpenID Connect plugin may query the provider to
   150  	// update the keys, causing performance hits.
   151  	if config.AuthenticationConfig != nil {
   152  		for _, jwtAuthenticator := range config.AuthenticationConfig.JWT {
   153  			var oidcCAContent oidc.CAContentProvider
   154  			if len(jwtAuthenticator.Issuer.CertificateAuthority) > 0 {
   155  				var oidcCAError error
   156  				oidcCAContent, oidcCAError = dynamiccertificates.NewStaticCAContent("oidc-authenticator", []byte(jwtAuthenticator.Issuer.CertificateAuthority))
   157  				if oidcCAError != nil {
   158  					return nil, nil, nil, oidcCAError
   159  				}
   160  			}
   161  			oidcAuth, err := oidc.New(oidc.Options{
   162  				JWTAuthenticator:     jwtAuthenticator,
   163  				CAContentProvider:    oidcCAContent,
   164  				SupportedSigningAlgs: config.OIDCSigningAlgs,
   165  			})
   166  			if err != nil {
   167  				return nil, nil, nil, err
   168  			}
   169  			tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, oidcAuth))
   170  		}
   171  	}
   172  
   173  	if len(config.WebhookTokenAuthnConfigFile) > 0 {
   174  		webhookTokenAuth, err := newWebhookTokenAuthenticator(config)
   175  		if err != nil {
   176  			return nil, nil, nil, err
   177  		}
   178  
   179  		tokenAuthenticators = append(tokenAuthenticators, webhookTokenAuth)
   180  	}
   181  
   182  	if len(tokenAuthenticators) > 0 {
   183  		// Union the token authenticators
   184  		tokenAuth := tokenunion.New(tokenAuthenticators...)
   185  		// Optionally cache authentication results
   186  		if config.TokenSuccessCacheTTL > 0 || config.TokenFailureCacheTTL > 0 {
   187  			tokenAuth = tokencache.New(tokenAuth, true, config.TokenSuccessCacheTTL, config.TokenFailureCacheTTL)
   188  		}
   189  		authenticators = append(authenticators, bearertoken.New(tokenAuth), websocket.NewProtocolAuthenticator(tokenAuth))
   190  
   191  		securityDefinitionsV2["BearerToken"] = &spec.SecurityScheme{
   192  			SecuritySchemeProps: spec.SecuritySchemeProps{
   193  				Type:        "apiKey",
   194  				Name:        "authorization",
   195  				In:          "header",
   196  				Description: "Bearer Token authentication",
   197  			},
   198  		}
   199  		securitySchemesV3["BearerToken"] = &spec3.SecurityScheme{
   200  			SecuritySchemeProps: spec3.SecuritySchemeProps{
   201  				Type:        "apiKey",
   202  				Name:        "authorization",
   203  				In:          "header",
   204  				Description: "Bearer Token authentication",
   205  			},
   206  		}
   207  	}
   208  
   209  	if len(authenticators) == 0 {
   210  		if config.Anonymous {
   211  			return anonymous.NewAuthenticator(), &securityDefinitionsV2, securitySchemesV3, nil
   212  		}
   213  		return nil, &securityDefinitionsV2, securitySchemesV3, nil
   214  	}
   215  
   216  	authenticator := union.New(authenticators...)
   217  
   218  	authenticator = group.NewAuthenticatedGroupAdder(authenticator)
   219  
   220  	if config.Anonymous {
   221  		// If the authenticator chain returns an error, return an error (don't consider a bad bearer token
   222  		// or invalid username/password combination anonymous).
   223  		authenticator = union.NewFailOnError(authenticator, anonymous.NewAuthenticator())
   224  	}
   225  
   226  	return authenticator, &securityDefinitionsV2, securitySchemesV3, nil
   227  }
   228  
   229  // IsValidServiceAccountKeyFile returns true if a valid public RSA key can be read from the given file
   230  func IsValidServiceAccountKeyFile(file string) bool {
   231  	_, err := keyutil.PublicKeysFromFile(file)
   232  	return err == nil
   233  }
   234  
   235  // newAuthenticatorFromTokenFile returns an authenticator.Token or an error
   236  func newAuthenticatorFromTokenFile(tokenAuthFile string) (authenticator.Token, error) {
   237  	tokenAuthenticator, err := tokenfile.NewCSV(tokenAuthFile)
   238  	if err != nil {
   239  		return nil, err
   240  	}
   241  
   242  	return tokenAuthenticator, nil
   243  }
   244  
   245  // newLegacyServiceAccountAuthenticator returns an authenticator.Token or an error
   246  func newLegacyServiceAccountAuthenticator(keyfiles []string, lookup bool, apiAudiences authenticator.Audiences, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter, secretsWriter typedv1core.SecretsGetter) (authenticator.Token, error) {
   247  	allPublicKeys := []interface{}{}
   248  	for _, keyfile := range keyfiles {
   249  		publicKeys, err := keyutil.PublicKeysFromFile(keyfile)
   250  		if err != nil {
   251  			return nil, err
   252  		}
   253  		allPublicKeys = append(allPublicKeys, publicKeys...)
   254  	}
   255  	validator, err := serviceaccount.NewLegacyValidator(lookup, serviceAccountGetter, secretsWriter)
   256  	if err != nil {
   257  		return nil, fmt.Errorf("while creating legacy validator, err: %w", err)
   258  	}
   259  
   260  	tokenAuthenticator := serviceaccount.JWTTokenAuthenticator([]string{serviceaccount.LegacyIssuer}, allPublicKeys, apiAudiences, validator)
   261  	return tokenAuthenticator, nil
   262  }
   263  
   264  // newServiceAccountAuthenticator returns an authenticator.Token or an error
   265  func newServiceAccountAuthenticator(issuers []string, keyfiles []string, apiAudiences authenticator.Audiences, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
   266  	allPublicKeys := []interface{}{}
   267  	for _, keyfile := range keyfiles {
   268  		publicKeys, err := keyutil.PublicKeysFromFile(keyfile)
   269  		if err != nil {
   270  			return nil, err
   271  		}
   272  		allPublicKeys = append(allPublicKeys, publicKeys...)
   273  	}
   274  
   275  	tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(issuers, allPublicKeys, apiAudiences, serviceaccount.NewValidator(serviceAccountGetter))
   276  	return tokenAuthenticator, nil
   277  }
   278  
   279  func newWebhookTokenAuthenticator(config Config) (authenticator.Token, error) {
   280  	if config.WebhookRetryBackoff == nil {
   281  		return nil, errors.New("retry backoff parameters for authentication webhook has not been specified")
   282  	}
   283  
   284  	clientConfig, err := webhookutil.LoadKubeconfig(config.WebhookTokenAuthnConfigFile, config.CustomDial)
   285  	if err != nil {
   286  		return nil, err
   287  	}
   288  	webhookTokenAuthenticator, err := webhook.New(clientConfig, config.WebhookTokenAuthnVersion, config.APIAudiences, *config.WebhookRetryBackoff)
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  
   293  	return tokencache.New(webhookTokenAuthenticator, false, config.WebhookTokenAuthnCacheTTL, config.WebhookTokenAuthnCacheTTL), nil
   294  }