...

Source file src/k8s.io/kubernetes/pkg/kubeapiserver/authenticator/config.go

Documentation: k8s.io/kubernetes/pkg/kubeapiserver/authenticator

     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  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"sync/atomic"
    24  	"time"
    25  
    26  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    27  	utilnet "k8s.io/apimachinery/pkg/util/net"
    28  	"k8s.io/apimachinery/pkg/util/wait"
    29  	"k8s.io/apiserver/pkg/apis/apiserver"
    30  	"k8s.io/apiserver/pkg/authentication/authenticator"
    31  	"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
    32  	"k8s.io/apiserver/pkg/authentication/group"
    33  	"k8s.io/apiserver/pkg/authentication/request/anonymous"
    34  	"k8s.io/apiserver/pkg/authentication/request/bearertoken"
    35  	"k8s.io/apiserver/pkg/authentication/request/headerrequest"
    36  	"k8s.io/apiserver/pkg/authentication/request/union"
    37  	"k8s.io/apiserver/pkg/authentication/request/websocket"
    38  	"k8s.io/apiserver/pkg/authentication/request/x509"
    39  	tokencache "k8s.io/apiserver/pkg/authentication/token/cache"
    40  	"k8s.io/apiserver/pkg/authentication/token/tokenfile"
    41  	tokenunion "k8s.io/apiserver/pkg/authentication/token/union"
    42  	"k8s.io/apiserver/pkg/server/dynamiccertificates"
    43  	webhookutil "k8s.io/apiserver/pkg/util/webhook"
    44  	"k8s.io/apiserver/plugin/pkg/authenticator/token/oidc"
    45  	"k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
    46  	typedv1core "k8s.io/client-go/kubernetes/typed/core/v1"
    47  	"k8s.io/kube-openapi/pkg/spec3"
    48  	"k8s.io/kube-openapi/pkg/validation/spec"
    49  
    50  	// Initialize all known client auth plugins.
    51  	_ "k8s.io/client-go/plugin/pkg/client/auth"
    52  	"k8s.io/client-go/util/keyutil"
    53  	"k8s.io/kubernetes/pkg/serviceaccount"
    54  )
    55  
    56  // Config contains the data on how to authenticate a request to the Kube API Server
    57  type Config struct {
    58  	Anonymous      bool
    59  	BootstrapToken bool
    60  
    61  	TokenAuthFile               string
    62  	AuthenticationConfig        *apiserver.AuthenticationConfiguration
    63  	AuthenticationConfigData    string
    64  	OIDCSigningAlgs             []string
    65  	ServiceAccountKeyFiles      []string
    66  	ServiceAccountLookup        bool
    67  	ServiceAccountIssuers       []string
    68  	APIAudiences                authenticator.Audiences
    69  	WebhookTokenAuthnConfigFile string
    70  	WebhookTokenAuthnVersion    string
    71  	WebhookTokenAuthnCacheTTL   time.Duration
    72  	// WebhookRetryBackoff specifies the backoff parameters for the authentication webhook retry logic.
    73  	// This allows us to configure the sleep time at each iteration and the maximum number of retries allowed
    74  	// before we fail the webhook call in order to limit the fan out that ensues when the system is degraded.
    75  	WebhookRetryBackoff *wait.Backoff
    76  
    77  	TokenSuccessCacheTTL time.Duration
    78  	TokenFailureCacheTTL time.Duration
    79  
    80  	RequestHeaderConfig *authenticatorfactory.RequestHeaderConfig
    81  
    82  	// TODO, this is the only non-serializable part of the entire config.  Factor it out into a clientconfig
    83  	ServiceAccountTokenGetter   serviceaccount.ServiceAccountTokenGetter
    84  	SecretsWriter               typedv1core.SecretsGetter
    85  	BootstrapTokenAuthenticator authenticator.Token
    86  	// ClientCAContentProvider are the options for verifying incoming connections using mTLS and directly assigning to users.
    87  	// Generally this is the CA bundle file used to authenticate client certificates
    88  	// If this value is nil, then mutual TLS is disabled.
    89  	ClientCAContentProvider dynamiccertificates.CAContentProvider
    90  
    91  	// Optional field, custom dial function used to connect to webhook
    92  	CustomDial utilnet.DialFunc
    93  }
    94  
    95  // New returns an authenticator.Request or an error that supports the standard
    96  // Kubernetes authentication mechanisms.
    97  func (config Config) New(serverLifecycle context.Context) (authenticator.Request, func(context.Context, *apiserver.AuthenticationConfiguration) error, *spec.SecurityDefinitions, spec3.SecuritySchemes, error) {
    98  	var authenticators []authenticator.Request
    99  	var tokenAuthenticators []authenticator.Token
   100  	securityDefinitionsV2 := spec.SecurityDefinitions{}
   101  	securitySchemesV3 := spec3.SecuritySchemes{}
   102  
   103  	// front-proxy, BasicAuth methods, local first, then remote
   104  	// Add the front proxy authenticator if requested
   105  	if config.RequestHeaderConfig != nil {
   106  		requestHeaderAuthenticator := headerrequest.NewDynamicVerifyOptionsSecure(
   107  			config.RequestHeaderConfig.CAContentProvider.VerifyOptions,
   108  			config.RequestHeaderConfig.AllowedClientNames,
   109  			config.RequestHeaderConfig.UsernameHeaders,
   110  			config.RequestHeaderConfig.GroupHeaders,
   111  			config.RequestHeaderConfig.ExtraHeaderPrefixes,
   112  		)
   113  		authenticators = append(authenticators, authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, requestHeaderAuthenticator))
   114  	}
   115  
   116  	// X509 methods
   117  	if config.ClientCAContentProvider != nil {
   118  		certAuth := x509.NewDynamic(config.ClientCAContentProvider.VerifyOptions, x509.CommonNameUserConversion)
   119  		authenticators = append(authenticators, certAuth)
   120  	}
   121  
   122  	// Bearer token methods, local first, then remote
   123  	if len(config.TokenAuthFile) > 0 {
   124  		tokenAuth, err := newAuthenticatorFromTokenFile(config.TokenAuthFile)
   125  		if err != nil {
   126  			return nil, nil, nil, nil, err
   127  		}
   128  		tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, tokenAuth))
   129  	}
   130  	if len(config.ServiceAccountKeyFiles) > 0 {
   131  		serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.APIAudiences, config.ServiceAccountTokenGetter, config.SecretsWriter)
   132  		if err != nil {
   133  			return nil, nil, nil, nil, err
   134  		}
   135  		tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
   136  	}
   137  	if len(config.ServiceAccountIssuers) > 0 {
   138  		serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuers, config.ServiceAccountKeyFiles, config.APIAudiences, config.ServiceAccountTokenGetter)
   139  		if err != nil {
   140  			return nil, nil, nil, nil, err
   141  		}
   142  		tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
   143  	}
   144  
   145  	if config.BootstrapToken && config.BootstrapTokenAuthenticator != nil {
   146  		tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, config.BootstrapTokenAuthenticator))
   147  	}
   148  
   149  	// NOTE(ericchiang): Keep the OpenID Connect after Service Accounts.
   150  	//
   151  	// Because both plugins verify JWTs whichever comes first in the union experiences
   152  	// cache misses for all requests using the other. While the service account plugin
   153  	// simply returns an error, the OpenID Connect plugin may query the provider to
   154  	// update the keys, causing performance hits.
   155  	var updateAuthenticationConfig func(context.Context, *apiserver.AuthenticationConfiguration) error
   156  	if config.AuthenticationConfig != nil {
   157  		initialJWTAuthenticator, err := newJWTAuthenticator(serverLifecycle, config.AuthenticationConfig, config.OIDCSigningAlgs, config.APIAudiences, config.ServiceAccountIssuers)
   158  		if err != nil {
   159  			return nil, nil, nil, nil, err
   160  		}
   161  
   162  		jwtAuthenticatorPtr := &atomic.Pointer[jwtAuthenticatorWithCancel]{}
   163  		jwtAuthenticatorPtr.Store(initialJWTAuthenticator)
   164  
   165  		updateAuthenticationConfig = (&authenticationConfigUpdater{
   166  			serverLifecycle:     serverLifecycle,
   167  			config:              config,
   168  			jwtAuthenticatorPtr: jwtAuthenticatorPtr,
   169  		}).updateAuthenticationConfig
   170  
   171  		tokenAuthenticators = append(tokenAuthenticators,
   172  			authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
   173  				return jwtAuthenticatorPtr.Load().jwtAuthenticator.AuthenticateToken(ctx, token)
   174  			}),
   175  		)
   176  	}
   177  
   178  	if len(config.WebhookTokenAuthnConfigFile) > 0 {
   179  		webhookTokenAuth, err := newWebhookTokenAuthenticator(config)
   180  		if err != nil {
   181  			return nil, nil, nil, nil, err
   182  		}
   183  
   184  		tokenAuthenticators = append(tokenAuthenticators, webhookTokenAuth)
   185  	}
   186  
   187  	if len(tokenAuthenticators) > 0 {
   188  		// Union the token authenticators
   189  		tokenAuth := tokenunion.New(tokenAuthenticators...)
   190  		// Optionally cache authentication results
   191  		if config.TokenSuccessCacheTTL > 0 || config.TokenFailureCacheTTL > 0 {
   192  			tokenAuth = tokencache.New(tokenAuth, true, config.TokenSuccessCacheTTL, config.TokenFailureCacheTTL)
   193  		}
   194  		authenticators = append(authenticators, bearertoken.New(tokenAuth), websocket.NewProtocolAuthenticator(tokenAuth))
   195  
   196  		securityDefinitionsV2["BearerToken"] = &spec.SecurityScheme{
   197  			SecuritySchemeProps: spec.SecuritySchemeProps{
   198  				Type:        "apiKey",
   199  				Name:        "authorization",
   200  				In:          "header",
   201  				Description: "Bearer Token authentication",
   202  			},
   203  		}
   204  		securitySchemesV3["BearerToken"] = &spec3.SecurityScheme{
   205  			SecuritySchemeProps: spec3.SecuritySchemeProps{
   206  				Type:        "apiKey",
   207  				Name:        "authorization",
   208  				In:          "header",
   209  				Description: "Bearer Token authentication",
   210  			},
   211  		}
   212  	}
   213  
   214  	if len(authenticators) == 0 {
   215  		if config.Anonymous {
   216  			return anonymous.NewAuthenticator(), nil, &securityDefinitionsV2, securitySchemesV3, nil
   217  		}
   218  		return nil, nil, &securityDefinitionsV2, securitySchemesV3, nil
   219  	}
   220  
   221  	authenticator := union.New(authenticators...)
   222  
   223  	authenticator = group.NewAuthenticatedGroupAdder(authenticator)
   224  
   225  	if config.Anonymous {
   226  		// If the authenticator chain returns an error, return an error (don't consider a bad bearer token
   227  		// or invalid username/password combination anonymous).
   228  		authenticator = union.NewFailOnError(authenticator, anonymous.NewAuthenticator())
   229  	}
   230  
   231  	return authenticator, updateAuthenticationConfig, &securityDefinitionsV2, securitySchemesV3, nil
   232  }
   233  
   234  type jwtAuthenticatorWithCancel struct {
   235  	jwtAuthenticator authenticator.Token
   236  	healthCheck      func() error
   237  	cancel           func()
   238  }
   239  
   240  func newJWTAuthenticator(serverLifecycle context.Context, config *apiserver.AuthenticationConfiguration, oidcSigningAlgs []string, apiAudiences authenticator.Audiences, disallowedIssuers []string) (_ *jwtAuthenticatorWithCancel, buildErr error) {
   241  	ctx, cancel := context.WithCancel(serverLifecycle)
   242  
   243  	defer func() {
   244  		if buildErr != nil {
   245  			cancel()
   246  		}
   247  	}()
   248  	var jwtAuthenticators []authenticator.Token
   249  	var healthChecks []func() error
   250  	for _, jwtAuthenticator := range config.JWT {
   251  		// TODO remove this CAContentProvider indirection
   252  		var oidcCAContent oidc.CAContentProvider
   253  		if len(jwtAuthenticator.Issuer.CertificateAuthority) > 0 {
   254  			var oidcCAError error
   255  			oidcCAContent, oidcCAError = dynamiccertificates.NewStaticCAContent("oidc-authenticator", []byte(jwtAuthenticator.Issuer.CertificateAuthority))
   256  			if oidcCAError != nil {
   257  				return nil, oidcCAError
   258  			}
   259  		}
   260  		oidcAuth, err := oidc.New(ctx, oidc.Options{
   261  			JWTAuthenticator:     jwtAuthenticator,
   262  			CAContentProvider:    oidcCAContent,
   263  			SupportedSigningAlgs: oidcSigningAlgs,
   264  			DisallowedIssuers:    disallowedIssuers,
   265  		})
   266  		if err != nil {
   267  			return nil, err
   268  		}
   269  		jwtAuthenticators = append(jwtAuthenticators, oidcAuth)
   270  		healthChecks = append(healthChecks, oidcAuth.HealthCheck)
   271  	}
   272  	return &jwtAuthenticatorWithCancel{
   273  		jwtAuthenticator: authenticator.WrapAudienceAgnosticToken(apiAudiences, tokenunion.NewFailOnError(jwtAuthenticators...)), // this handles the empty jwtAuthenticators slice case correctly
   274  		healthCheck: func() error {
   275  			var errs []error
   276  			for _, check := range healthChecks {
   277  				if err := check(); err != nil {
   278  					errs = append(errs, err)
   279  				}
   280  			}
   281  			return utilerrors.NewAggregate(errs)
   282  		},
   283  		cancel: cancel,
   284  	}, nil
   285  }
   286  
   287  type authenticationConfigUpdater struct {
   288  	serverLifecycle     context.Context
   289  	config              Config
   290  	jwtAuthenticatorPtr *atomic.Pointer[jwtAuthenticatorWithCancel]
   291  }
   292  
   293  // the input ctx controls the timeout for updateAuthenticationConfig to return, not the lifetime of the constructed authenticators.
   294  func (c *authenticationConfigUpdater) updateAuthenticationConfig(ctx context.Context, authConfig *apiserver.AuthenticationConfiguration) error {
   295  	updatedJWTAuthenticator, err := newJWTAuthenticator(c.serverLifecycle, authConfig, c.config.OIDCSigningAlgs, c.config.APIAudiences, c.config.ServiceAccountIssuers)
   296  	if err != nil {
   297  		return err
   298  	}
   299  
   300  	var lastErr error
   301  	if waitErr := wait.PollUntilContextCancel(ctx, 10*time.Second, true, func(_ context.Context) (done bool, err error) {
   302  		lastErr = updatedJWTAuthenticator.healthCheck()
   303  		return lastErr == nil, nil
   304  	}); lastErr != nil || waitErr != nil {
   305  		updatedJWTAuthenticator.cancel()
   306  		return utilerrors.NewAggregate([]error{lastErr, waitErr}) // filters out nil errors
   307  	}
   308  
   309  	oldJWTAuthenticator := c.jwtAuthenticatorPtr.Swap(updatedJWTAuthenticator)
   310  	go func() {
   311  		t := time.NewTimer(time.Minute)
   312  		defer t.Stop()
   313  		select {
   314  		case <-c.serverLifecycle.Done():
   315  		case <-t.C:
   316  		}
   317  		// TODO maybe track requests so we know when this is safe to do
   318  		oldJWTAuthenticator.cancel()
   319  	}()
   320  
   321  	return nil
   322  }
   323  
   324  // IsValidServiceAccountKeyFile returns true if a valid public RSA key can be read from the given file
   325  func IsValidServiceAccountKeyFile(file string) bool {
   326  	_, err := keyutil.PublicKeysFromFile(file)
   327  	return err == nil
   328  }
   329  
   330  // newAuthenticatorFromTokenFile returns an authenticator.Token or an error
   331  func newAuthenticatorFromTokenFile(tokenAuthFile string) (authenticator.Token, error) {
   332  	tokenAuthenticator, err := tokenfile.NewCSV(tokenAuthFile)
   333  	if err != nil {
   334  		return nil, err
   335  	}
   336  
   337  	return tokenAuthenticator, nil
   338  }
   339  
   340  // newLegacyServiceAccountAuthenticator returns an authenticator.Token or an error
   341  func newLegacyServiceAccountAuthenticator(keyfiles []string, lookup bool, apiAudiences authenticator.Audiences, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter, secretsWriter typedv1core.SecretsGetter) (authenticator.Token, error) {
   342  	allPublicKeys := []interface{}{}
   343  	for _, keyfile := range keyfiles {
   344  		publicKeys, err := keyutil.PublicKeysFromFile(keyfile)
   345  		if err != nil {
   346  			return nil, err
   347  		}
   348  		allPublicKeys = append(allPublicKeys, publicKeys...)
   349  	}
   350  	validator, err := serviceaccount.NewLegacyValidator(lookup, serviceAccountGetter, secretsWriter)
   351  	if err != nil {
   352  		return nil, fmt.Errorf("while creating legacy validator, err: %w", err)
   353  	}
   354  
   355  	tokenAuthenticator := serviceaccount.JWTTokenAuthenticator([]string{serviceaccount.LegacyIssuer}, allPublicKeys, apiAudiences, validator)
   356  	return tokenAuthenticator, nil
   357  }
   358  
   359  // newServiceAccountAuthenticator returns an authenticator.Token or an error
   360  func newServiceAccountAuthenticator(issuers []string, keyfiles []string, apiAudiences authenticator.Audiences, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
   361  	allPublicKeys := []interface{}{}
   362  	for _, keyfile := range keyfiles {
   363  		publicKeys, err := keyutil.PublicKeysFromFile(keyfile)
   364  		if err != nil {
   365  			return nil, err
   366  		}
   367  		allPublicKeys = append(allPublicKeys, publicKeys...)
   368  	}
   369  
   370  	tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(issuers, allPublicKeys, apiAudiences, serviceaccount.NewValidator(serviceAccountGetter))
   371  	return tokenAuthenticator, nil
   372  }
   373  
   374  func newWebhookTokenAuthenticator(config Config) (authenticator.Token, error) {
   375  	if config.WebhookRetryBackoff == nil {
   376  		return nil, errors.New("retry backoff parameters for authentication webhook has not been specified")
   377  	}
   378  
   379  	clientConfig, err := webhookutil.LoadKubeconfig(config.WebhookTokenAuthnConfigFile, config.CustomDial)
   380  	if err != nil {
   381  		return nil, err
   382  	}
   383  	webhookTokenAuthenticator, err := webhook.New(clientConfig, config.WebhookTokenAuthnVersion, config.APIAudiences, *config.WebhookRetryBackoff)
   384  	if err != nil {
   385  		return nil, err
   386  	}
   387  
   388  	return tokencache.New(webhookTokenAuthenticator, false, config.WebhookTokenAuthnCacheTTL, config.WebhookTokenAuthnCacheTTL), nil
   389  }
   390  

View as plain text