...

Source file src/k8s.io/kubernetes/pkg/kubeapiserver/options/authentication.go

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

     1  /*
     2  Copyright 2016 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 options
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"net/url"
    24  	"os"
    25  	"strings"
    26  	"sync"
    27  	"time"
    28  
    29  	"github.com/spf13/pflag"
    30  
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/runtime"
    33  	"k8s.io/apimachinery/pkg/runtime/serializer"
    34  	"k8s.io/apimachinery/pkg/util/sets"
    35  	"k8s.io/apimachinery/pkg/util/wait"
    36  	"k8s.io/apiserver/pkg/apis/apiserver"
    37  	"k8s.io/apiserver/pkg/apis/apiserver/install"
    38  	apiservervalidation "k8s.io/apiserver/pkg/apis/apiserver/validation"
    39  	"k8s.io/apiserver/pkg/authentication/authenticator"
    40  	genericfeatures "k8s.io/apiserver/pkg/features"
    41  	genericapiserver "k8s.io/apiserver/pkg/server"
    42  	"k8s.io/apiserver/pkg/server/egressselector"
    43  	genericoptions "k8s.io/apiserver/pkg/server/options"
    44  	authenticationconfigmetrics "k8s.io/apiserver/pkg/server/options/authenticationconfig/metrics"
    45  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    46  	"k8s.io/apiserver/plugin/pkg/authenticator/token/oidc"
    47  	"k8s.io/client-go/informers"
    48  	"k8s.io/client-go/kubernetes"
    49  	v1listers "k8s.io/client-go/listers/core/v1"
    50  	cliflag "k8s.io/component-base/cli/flag"
    51  	"k8s.io/klog/v2"
    52  	openapicommon "k8s.io/kube-openapi/pkg/common"
    53  	serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
    54  	"k8s.io/kubernetes/pkg/features"
    55  	kubeauthenticator "k8s.io/kubernetes/pkg/kubeapiserver/authenticator"
    56  	authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
    57  	"k8s.io/kubernetes/pkg/util/filesystem"
    58  	"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/bootstrap"
    59  	"k8s.io/utils/pointer"
    60  )
    61  
    62  const (
    63  	oidcIssuerURLFlag      = "oidc-issuer-url"
    64  	oidcClientIDFlag       = "oidc-client-id"
    65  	oidcCAFileFlag         = "oidc-ca-file"
    66  	oidcUsernameClaimFlag  = "oidc-username-claim"
    67  	oidcUsernamePrefixFlag = "oidc-username-prefix"
    68  	oidcGroupsClaimFlag    = "oidc-groups-claim"
    69  	oidcGroupsPrefixFlag   = "oidc-groups-prefix"
    70  	oidcSigningAlgsFlag    = "oidc-signing-algs"
    71  	oidcRequiredClaimFlag  = "oidc-required-claim"
    72  )
    73  
    74  // UpdateAuthenticationConfigTimeout controls how long we wait for calls to updateAuthenticationConfig to succeed.
    75  // Exported as a variable so that it can be overridden in integration tests.
    76  var UpdateAuthenticationConfigTimeout = time.Minute
    77  
    78  // BuiltInAuthenticationOptions contains all build-in authentication options for API Server
    79  type BuiltInAuthenticationOptions struct {
    80  	APIAudiences    []string
    81  	Anonymous       *AnonymousAuthenticationOptions
    82  	BootstrapToken  *BootstrapTokenAuthenticationOptions
    83  	ClientCert      *genericoptions.ClientCertAuthenticationOptions
    84  	OIDC            *OIDCAuthenticationOptions
    85  	RequestHeader   *genericoptions.RequestHeaderAuthenticationOptions
    86  	ServiceAccounts *ServiceAccountAuthenticationOptions
    87  	TokenFile       *TokenFileAuthenticationOptions
    88  	WebHook         *WebHookAuthenticationOptions
    89  
    90  	AuthenticationConfigFile string
    91  
    92  	TokenSuccessCacheTTL time.Duration
    93  	TokenFailureCacheTTL time.Duration
    94  }
    95  
    96  // AnonymousAuthenticationOptions contains anonymous authentication options for API Server
    97  type AnonymousAuthenticationOptions struct {
    98  	Allow bool
    99  }
   100  
   101  // BootstrapTokenAuthenticationOptions contains bootstrap token authentication options for API Server
   102  type BootstrapTokenAuthenticationOptions struct {
   103  	Enable bool
   104  }
   105  
   106  // OIDCAuthenticationOptions contains OIDC authentication options for API Server
   107  type OIDCAuthenticationOptions struct {
   108  	CAFile         string
   109  	ClientID       string
   110  	IssuerURL      string
   111  	UsernameClaim  string
   112  	UsernamePrefix string
   113  	GroupsClaim    string
   114  	GroupsPrefix   string
   115  	SigningAlgs    []string
   116  	RequiredClaims map[string]string
   117  
   118  	// areFlagsConfigured is a function that returns true if any of the oidc-* flags are configured.
   119  	areFlagsConfigured func() bool
   120  }
   121  
   122  // ServiceAccountAuthenticationOptions contains service account authentication options for API Server
   123  type ServiceAccountAuthenticationOptions struct {
   124  	KeyFiles         []string
   125  	Lookup           bool
   126  	Issuers          []string
   127  	JWKSURI          string
   128  	MaxExpiration    time.Duration
   129  	ExtendExpiration bool
   130  }
   131  
   132  // TokenFileAuthenticationOptions contains token file authentication options for API Server
   133  type TokenFileAuthenticationOptions struct {
   134  	TokenFile string
   135  }
   136  
   137  // WebHookAuthenticationOptions contains web hook authentication options for API Server
   138  type WebHookAuthenticationOptions struct {
   139  	ConfigFile string
   140  	Version    string
   141  	CacheTTL   time.Duration
   142  
   143  	// RetryBackoff specifies the backoff parameters for the authentication webhook retry logic.
   144  	// This allows us to configure the sleep time at each iteration and the maximum number of retries allowed
   145  	// before we fail the webhook call in order to limit the fan out that ensues when the system is degraded.
   146  	RetryBackoff *wait.Backoff
   147  }
   148  
   149  // NewBuiltInAuthenticationOptions create a new BuiltInAuthenticationOptions, just set default token cache TTL
   150  func NewBuiltInAuthenticationOptions() *BuiltInAuthenticationOptions {
   151  	return &BuiltInAuthenticationOptions{
   152  		TokenSuccessCacheTTL: 10 * time.Second,
   153  		TokenFailureCacheTTL: 0 * time.Second,
   154  	}
   155  }
   156  
   157  // WithAll set default value for every build-in authentication option
   158  func (o *BuiltInAuthenticationOptions) WithAll() *BuiltInAuthenticationOptions {
   159  	return o.
   160  		WithAnonymous().
   161  		WithBootstrapToken().
   162  		WithClientCert().
   163  		WithOIDC().
   164  		WithRequestHeader().
   165  		WithServiceAccounts().
   166  		WithTokenFile().
   167  		WithWebHook()
   168  }
   169  
   170  // WithAnonymous set default value for anonymous authentication
   171  func (o *BuiltInAuthenticationOptions) WithAnonymous() *BuiltInAuthenticationOptions {
   172  	o.Anonymous = &AnonymousAuthenticationOptions{Allow: true}
   173  	return o
   174  }
   175  
   176  // WithBootstrapToken set default value for bootstrap token authentication
   177  func (o *BuiltInAuthenticationOptions) WithBootstrapToken() *BuiltInAuthenticationOptions {
   178  	o.BootstrapToken = &BootstrapTokenAuthenticationOptions{}
   179  	return o
   180  }
   181  
   182  // WithClientCert set default value for client cert
   183  func (o *BuiltInAuthenticationOptions) WithClientCert() *BuiltInAuthenticationOptions {
   184  	o.ClientCert = &genericoptions.ClientCertAuthenticationOptions{}
   185  	return o
   186  }
   187  
   188  // WithOIDC set default value for OIDC authentication
   189  func (o *BuiltInAuthenticationOptions) WithOIDC() *BuiltInAuthenticationOptions {
   190  	o.OIDC = &OIDCAuthenticationOptions{areFlagsConfigured: func() bool { return false }}
   191  	return o
   192  }
   193  
   194  // WithRequestHeader set default value for request header authentication
   195  func (o *BuiltInAuthenticationOptions) WithRequestHeader() *BuiltInAuthenticationOptions {
   196  	o.RequestHeader = &genericoptions.RequestHeaderAuthenticationOptions{}
   197  	return o
   198  }
   199  
   200  // WithServiceAccounts set default value for service account authentication
   201  func (o *BuiltInAuthenticationOptions) WithServiceAccounts() *BuiltInAuthenticationOptions {
   202  	o.ServiceAccounts = &ServiceAccountAuthenticationOptions{Lookup: true, ExtendExpiration: true}
   203  	return o
   204  }
   205  
   206  // WithTokenFile set default value for token file authentication
   207  func (o *BuiltInAuthenticationOptions) WithTokenFile() *BuiltInAuthenticationOptions {
   208  	o.TokenFile = &TokenFileAuthenticationOptions{}
   209  	return o
   210  }
   211  
   212  // WithWebHook set default value for web hook authentication
   213  func (o *BuiltInAuthenticationOptions) WithWebHook() *BuiltInAuthenticationOptions {
   214  	o.WebHook = &WebHookAuthenticationOptions{
   215  		Version:      "v1beta1",
   216  		CacheTTL:     2 * time.Minute,
   217  		RetryBackoff: genericoptions.DefaultAuthWebhookRetryBackoff(),
   218  	}
   219  	return o
   220  }
   221  
   222  // Validate checks invalid config combination
   223  func (o *BuiltInAuthenticationOptions) Validate() []error {
   224  	if o == nil {
   225  		return nil
   226  	}
   227  
   228  	var allErrors []error
   229  
   230  	allErrors = append(allErrors, o.validateOIDCOptions()...)
   231  
   232  	if o.ServiceAccounts != nil && len(o.ServiceAccounts.Issuers) > 0 {
   233  		seen := make(map[string]bool)
   234  		for _, issuer := range o.ServiceAccounts.Issuers {
   235  			if strings.Contains(issuer, ":") {
   236  				if _, err := url.Parse(issuer); err != nil {
   237  					allErrors = append(allErrors, fmt.Errorf("service-account-issuer %q contained a ':' but was not a valid URL: %v", issuer, err))
   238  					continue
   239  				}
   240  			}
   241  			if issuer == "" {
   242  				allErrors = append(allErrors, fmt.Errorf("service-account-issuer should not be an empty string"))
   243  				continue
   244  			}
   245  			if seen[issuer] {
   246  				allErrors = append(allErrors, fmt.Errorf("service-account-issuer %q is already specified", issuer))
   247  				continue
   248  			}
   249  			seen[issuer] = true
   250  		}
   251  	}
   252  
   253  	if o.ServiceAccounts != nil {
   254  		if len(o.ServiceAccounts.Issuers) == 0 {
   255  			allErrors = append(allErrors, errors.New("service-account-issuer is a required flag"))
   256  		}
   257  		if len(o.ServiceAccounts.KeyFiles) == 0 {
   258  			allErrors = append(allErrors, errors.New("service-account-key-file is a required flag"))
   259  		}
   260  
   261  		// Validate the JWKS URI when it is explicitly set.
   262  		// When unset, it is later derived from ExternalHost.
   263  		if o.ServiceAccounts.JWKSURI != "" {
   264  			if u, err := url.Parse(o.ServiceAccounts.JWKSURI); err != nil {
   265  				allErrors = append(allErrors, fmt.Errorf("service-account-jwks-uri must be a valid URL: %v", err))
   266  			} else if u.Scheme != "https" {
   267  				allErrors = append(allErrors, fmt.Errorf("service-account-jwks-uri requires https scheme, parsed as: %v", u.String()))
   268  			}
   269  		}
   270  	}
   271  
   272  	// verify that if ServiceAccountTokenNodeBinding is enabled, ServiceAccountTokenNodeBindingValidation is also enabled.
   273  	if utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenNodeBinding) && !utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenNodeBindingValidation) {
   274  		allErrors = append(allErrors, fmt.Errorf("the %q feature gate can only be enabled if the %q feature gate is also enabled", features.ServiceAccountTokenNodeBinding, features.ServiceAccountTokenNodeBindingValidation))
   275  	}
   276  
   277  	if o.WebHook != nil {
   278  		retryBackoff := o.WebHook.RetryBackoff
   279  		if retryBackoff != nil && retryBackoff.Steps <= 0 {
   280  			allErrors = append(allErrors, fmt.Errorf("number of webhook retry attempts must be greater than 0, but is: %d", retryBackoff.Steps))
   281  		}
   282  	}
   283  
   284  	if o.RequestHeader != nil {
   285  		allErrors = append(allErrors, o.RequestHeader.Validate()...)
   286  	}
   287  
   288  	return allErrors
   289  }
   290  
   291  // AddFlags returns flags of authentication for a API Server
   292  func (o *BuiltInAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
   293  	if o == nil {
   294  		return
   295  	}
   296  
   297  	fs.StringSliceVar(&o.APIAudiences, "api-audiences", o.APIAudiences, ""+
   298  		"Identifiers of the API. The service account token authenticator will validate that "+
   299  		"tokens used against the API are bound to at least one of these audiences. If the "+
   300  		"--service-account-issuer flag is configured and this flag is not, this field "+
   301  		"defaults to a single element list containing the issuer URL.")
   302  
   303  	if o.Anonymous != nil {
   304  		fs.BoolVar(&o.Anonymous.Allow, "anonymous-auth", o.Anonymous.Allow, ""+
   305  			"Enables anonymous requests to the secure port of the API server. "+
   306  			"Requests that are not rejected by another authentication method are treated as anonymous requests. "+
   307  			"Anonymous requests have a username of system:anonymous, and a group name of system:unauthenticated.")
   308  	}
   309  
   310  	if o.BootstrapToken != nil {
   311  		fs.BoolVar(&o.BootstrapToken.Enable, "enable-bootstrap-token-auth", o.BootstrapToken.Enable, ""+
   312  			"Enable to allow secrets of type 'bootstrap.kubernetes.io/token' in the 'kube-system' "+
   313  			"namespace to be used for TLS bootstrapping authentication.")
   314  	}
   315  
   316  	if o.ClientCert != nil {
   317  		o.ClientCert.AddFlags(fs)
   318  	}
   319  
   320  	if o.OIDC != nil {
   321  		fs.StringVar(&o.OIDC.IssuerURL, oidcIssuerURLFlag, o.OIDC.IssuerURL, ""+
   322  			"The URL of the OpenID issuer, only HTTPS scheme will be accepted. "+
   323  			"If set, it will be used to verify the OIDC JSON Web Token (JWT).")
   324  
   325  		fs.StringVar(&o.OIDC.ClientID, oidcClientIDFlag, o.OIDC.ClientID,
   326  			"The client ID for the OpenID Connect client, must be set if oidc-issuer-url is set.")
   327  
   328  		fs.StringVar(&o.OIDC.CAFile, oidcCAFileFlag, o.OIDC.CAFile, ""+
   329  			"If set, the OpenID server's certificate will be verified by one of the authorities "+
   330  			"in the oidc-ca-file, otherwise the host's root CA set will be used.")
   331  
   332  		fs.StringVar(&o.OIDC.UsernameClaim, oidcUsernameClaimFlag, "sub", ""+
   333  			"The OpenID claim to use as the user name. Note that claims other than the default ('sub') "+
   334  			"is not guaranteed to be unique and immutable. This flag is experimental, please see "+
   335  			"the authentication documentation for further details.")
   336  
   337  		fs.StringVar(&o.OIDC.UsernamePrefix, oidcUsernamePrefixFlag, "", ""+
   338  			"If provided, all usernames will be prefixed with this value. If not provided, "+
   339  			"username claims other than 'email' are prefixed by the issuer URL to avoid "+
   340  			"clashes. To skip any prefixing, provide the value '-'.")
   341  
   342  		fs.StringVar(&o.OIDC.GroupsClaim, oidcGroupsClaimFlag, "", ""+
   343  			"If provided, the name of a custom OpenID Connect claim for specifying user groups. "+
   344  			"The claim value is expected to be a string or array of strings. This flag is experimental, "+
   345  			"please see the authentication documentation for further details.")
   346  
   347  		fs.StringVar(&o.OIDC.GroupsPrefix, oidcGroupsPrefixFlag, "", ""+
   348  			"If provided, all groups will be prefixed with this value to prevent conflicts with "+
   349  			"other authentication strategies.")
   350  
   351  		fs.StringSliceVar(&o.OIDC.SigningAlgs, oidcSigningAlgsFlag, []string{"RS256"}, ""+
   352  			"Comma-separated list of allowed JOSE asymmetric signing algorithms. JWTs with a "+
   353  			"supported 'alg' header values are: RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512. "+
   354  			"Values are defined by RFC 7518 https://tools.ietf.org/html/rfc7518#section-3.1.")
   355  
   356  		fs.Var(cliflag.NewMapStringStringNoSplit(&o.OIDC.RequiredClaims), oidcRequiredClaimFlag, ""+
   357  			"A key=value pair that describes a required claim in the ID Token. "+
   358  			"If set, the claim is verified to be present in the ID Token with a matching value. "+
   359  			"Repeat this flag to specify multiple claims.")
   360  
   361  		fs.StringVar(&o.AuthenticationConfigFile, "authentication-config", o.AuthenticationConfigFile, ""+
   362  			"File with Authentication Configuration to configure the JWT Token authenticator. "+
   363  			"Note: This feature is in Alpha since v1.29."+
   364  			"--feature-gate=StructuredAuthenticationConfiguration=true needs to be set for enabling this feature."+
   365  			"This feature is mutually exclusive with the oidc-* flags.")
   366  
   367  		o.OIDC.areFlagsConfigured = func() bool {
   368  			return fs.Changed(oidcIssuerURLFlag) ||
   369  				fs.Changed(oidcClientIDFlag) ||
   370  				fs.Changed(oidcCAFileFlag) ||
   371  				fs.Changed(oidcUsernameClaimFlag) ||
   372  				fs.Changed(oidcUsernamePrefixFlag) ||
   373  				fs.Changed(oidcGroupsClaimFlag) ||
   374  				fs.Changed(oidcGroupsPrefixFlag) ||
   375  				fs.Changed(oidcSigningAlgsFlag) ||
   376  				fs.Changed(oidcRequiredClaimFlag)
   377  		}
   378  	}
   379  
   380  	if o.RequestHeader != nil {
   381  		o.RequestHeader.AddFlags(fs)
   382  	}
   383  
   384  	if o.ServiceAccounts != nil {
   385  		fs.StringArrayVar(&o.ServiceAccounts.KeyFiles, "service-account-key-file", o.ServiceAccounts.KeyFiles, ""+
   386  			"File containing PEM-encoded x509 RSA or ECDSA private or public keys, used to verify "+
   387  			"ServiceAccount tokens. The specified file can contain multiple keys, and the flag can "+
   388  			"be specified multiple times with different files. If unspecified, "+
   389  			"--tls-private-key-file is used. Must be specified when "+
   390  			"--service-account-signing-key-file is provided")
   391  
   392  		fs.BoolVar(&o.ServiceAccounts.Lookup, "service-account-lookup", o.ServiceAccounts.Lookup,
   393  			"If true, validate ServiceAccount tokens exist in etcd as part of authentication.")
   394  
   395  		fs.StringArrayVar(&o.ServiceAccounts.Issuers, "service-account-issuer", o.ServiceAccounts.Issuers, ""+
   396  			"Identifier of the service account token issuer. The issuer will assert this identifier "+
   397  			"in \"iss\" claim of issued tokens. This value is a string or URI. If this option is not "+
   398  			"a valid URI per the OpenID Discovery 1.0 spec, the ServiceAccountIssuerDiscovery feature "+
   399  			"will remain disabled, even if the feature gate is set to true. It is highly recommended "+
   400  			"that this value comply with the OpenID spec: https://openid.net/specs/openid-connect-discovery-1_0.html. "+
   401  			"In practice, this means that service-account-issuer must be an https URL. It is also highly "+
   402  			"recommended that this URL be capable of serving OpenID discovery documents at "+
   403  			"{service-account-issuer}/.well-known/openid-configuration. "+
   404  			"When this flag is specified multiple times, the first is used to generate tokens "+
   405  			"and all are used to determine which issuers are accepted.")
   406  
   407  		fs.StringVar(&o.ServiceAccounts.JWKSURI, "service-account-jwks-uri", o.ServiceAccounts.JWKSURI, ""+
   408  			"Overrides the URI for the JSON Web Key Set in the discovery doc served at "+
   409  			"/.well-known/openid-configuration. This flag is useful if the discovery doc"+
   410  			"and key set are served to relying parties from a URL other than the "+
   411  			"API server's external (as auto-detected or overridden with external-hostname). ")
   412  
   413  		fs.DurationVar(&o.ServiceAccounts.MaxExpiration, "service-account-max-token-expiration", o.ServiceAccounts.MaxExpiration, ""+
   414  			"The maximum validity duration of a token created by the service account token issuer. If an otherwise valid "+
   415  			"TokenRequest with a validity duration larger than this value is requested, a token will be issued with a validity duration of this value.")
   416  
   417  		fs.BoolVar(&o.ServiceAccounts.ExtendExpiration, "service-account-extend-token-expiration", o.ServiceAccounts.ExtendExpiration, ""+
   418  			"Turns on projected service account expiration extension during token generation, "+
   419  			"which helps safe transition from legacy token to bound service account token feature. "+
   420  			"If this flag is enabled, admission injected tokens would be extended up to 1 year to "+
   421  			"prevent unexpected failure during transition, ignoring value of service-account-max-token-expiration.")
   422  	}
   423  
   424  	if o.TokenFile != nil {
   425  		fs.StringVar(&o.TokenFile.TokenFile, "token-auth-file", o.TokenFile.TokenFile, ""+
   426  			"If set, the file that will be used to secure the secure port of the API server "+
   427  			"via token authentication.")
   428  	}
   429  
   430  	if o.WebHook != nil {
   431  		fs.StringVar(&o.WebHook.ConfigFile, "authentication-token-webhook-config-file", o.WebHook.ConfigFile, ""+
   432  			"File with webhook configuration for token authentication in kubeconfig format. "+
   433  			"The API server will query the remote service to determine authentication for bearer tokens.")
   434  
   435  		fs.StringVar(&o.WebHook.Version, "authentication-token-webhook-version", o.WebHook.Version, ""+
   436  			"The API version of the authentication.k8s.io TokenReview to send to and expect from the webhook.")
   437  
   438  		fs.DurationVar(&o.WebHook.CacheTTL, "authentication-token-webhook-cache-ttl", o.WebHook.CacheTTL,
   439  			"The duration to cache responses from the webhook token authenticator.")
   440  	}
   441  }
   442  
   443  // ToAuthenticationConfig convert BuiltInAuthenticationOptions to kubeauthenticator.Config. Returns
   444  // an empty config if o is nil.
   445  func (o *BuiltInAuthenticationOptions) ToAuthenticationConfig() (kubeauthenticator.Config, error) {
   446  	if o == nil {
   447  		return kubeauthenticator.Config{}, nil
   448  	}
   449  
   450  	ret := kubeauthenticator.Config{
   451  		TokenSuccessCacheTTL: o.TokenSuccessCacheTTL,
   452  		TokenFailureCacheTTL: o.TokenFailureCacheTTL,
   453  	}
   454  
   455  	if o.Anonymous != nil {
   456  		ret.Anonymous = o.Anonymous.Allow
   457  	}
   458  
   459  	if o.BootstrapToken != nil {
   460  		ret.BootstrapToken = o.BootstrapToken.Enable
   461  	}
   462  
   463  	if o.ClientCert != nil {
   464  		var err error
   465  		ret.ClientCAContentProvider, err = o.ClientCert.GetClientCAContentProvider()
   466  		if err != nil {
   467  			return kubeauthenticator.Config{}, err
   468  		}
   469  	}
   470  
   471  	// When the StructuredAuthenticationConfiguration feature is enabled and the authentication config file is provided,
   472  	// load the authentication config from the file.
   473  	if len(o.AuthenticationConfigFile) > 0 {
   474  		var err error
   475  		if ret.AuthenticationConfig, ret.AuthenticationConfigData, err = loadAuthenticationConfig(o.AuthenticationConfigFile); err != nil {
   476  			return kubeauthenticator.Config{}, err
   477  		}
   478  		// all known signing algs are allowed when using authentication config
   479  		// TODO: what we really want to express is 'any alg is fine as long it matches a public key'
   480  		ret.OIDCSigningAlgs = oidc.AllValidSigningAlgorithms()
   481  	} else if o.OIDC != nil && len(o.OIDC.IssuerURL) > 0 && len(o.OIDC.ClientID) > 0 {
   482  		usernamePrefix := o.OIDC.UsernamePrefix
   483  
   484  		if o.OIDC.UsernamePrefix == "" && o.OIDC.UsernameClaim != "email" {
   485  			// Legacy CLI flag behavior. If a usernamePrefix isn't provided, prefix all claims other than "email"
   486  			// with the issuerURL.
   487  			//
   488  			// See https://github.com/kubernetes/kubernetes/issues/31380
   489  			usernamePrefix = o.OIDC.IssuerURL + "#"
   490  		}
   491  		if o.OIDC.UsernamePrefix == "-" {
   492  			// Special value indicating usernames shouldn't be prefixed.
   493  			usernamePrefix = ""
   494  		}
   495  
   496  		jwtAuthenticator := apiserver.JWTAuthenticator{
   497  			Issuer: apiserver.Issuer{
   498  				URL:       o.OIDC.IssuerURL,
   499  				Audiences: []string{o.OIDC.ClientID},
   500  			},
   501  			ClaimMappings: apiserver.ClaimMappings{
   502  				Username: apiserver.PrefixedClaimOrExpression{
   503  					Prefix: pointer.String(usernamePrefix),
   504  					Claim:  o.OIDC.UsernameClaim,
   505  				},
   506  			},
   507  		}
   508  
   509  		if len(o.OIDC.GroupsClaim) > 0 {
   510  			jwtAuthenticator.ClaimMappings.Groups = apiserver.PrefixedClaimOrExpression{
   511  				Prefix: pointer.String(o.OIDC.GroupsPrefix),
   512  				Claim:  o.OIDC.GroupsClaim,
   513  			}
   514  		}
   515  
   516  		if len(o.OIDC.CAFile) != 0 {
   517  			caContent, err := os.ReadFile(o.OIDC.CAFile)
   518  			if err != nil {
   519  				return kubeauthenticator.Config{}, err
   520  			}
   521  			jwtAuthenticator.Issuer.CertificateAuthority = string(caContent)
   522  		}
   523  
   524  		if len(o.OIDC.RequiredClaims) > 0 {
   525  			claimValidationRules := make([]apiserver.ClaimValidationRule, 0, len(o.OIDC.RequiredClaims))
   526  			for claim, value := range o.OIDC.RequiredClaims {
   527  				claimValidationRules = append(claimValidationRules, apiserver.ClaimValidationRule{
   528  					Claim:         claim,
   529  					RequiredValue: value,
   530  				})
   531  			}
   532  			jwtAuthenticator.ClaimValidationRules = claimValidationRules
   533  		}
   534  
   535  		authConfig := &apiserver.AuthenticationConfiguration{
   536  			JWT: []apiserver.JWTAuthenticator{jwtAuthenticator},
   537  		}
   538  
   539  		ret.AuthenticationConfig = authConfig
   540  		ret.OIDCSigningAlgs = o.OIDC.SigningAlgs
   541  	}
   542  
   543  	if ret.AuthenticationConfig != nil {
   544  		if err := apiservervalidation.ValidateAuthenticationConfiguration(ret.AuthenticationConfig, ret.ServiceAccountIssuers).ToAggregate(); err != nil {
   545  			return kubeauthenticator.Config{}, err
   546  		}
   547  	}
   548  
   549  	if o.RequestHeader != nil {
   550  		var err error
   551  		ret.RequestHeaderConfig, err = o.RequestHeader.ToAuthenticationRequestHeaderConfig()
   552  		if err != nil {
   553  			return kubeauthenticator.Config{}, err
   554  		}
   555  	}
   556  
   557  	ret.APIAudiences = o.APIAudiences
   558  	if o.ServiceAccounts != nil {
   559  		if len(o.ServiceAccounts.Issuers) != 0 && len(o.APIAudiences) == 0 {
   560  			ret.APIAudiences = authenticator.Audiences(o.ServiceAccounts.Issuers)
   561  		}
   562  		ret.ServiceAccountKeyFiles = o.ServiceAccounts.KeyFiles
   563  		ret.ServiceAccountIssuers = o.ServiceAccounts.Issuers
   564  		ret.ServiceAccountLookup = o.ServiceAccounts.Lookup
   565  	}
   566  
   567  	if o.TokenFile != nil {
   568  		ret.TokenAuthFile = o.TokenFile.TokenFile
   569  	}
   570  
   571  	if o.WebHook != nil {
   572  		ret.WebhookTokenAuthnConfigFile = o.WebHook.ConfigFile
   573  		ret.WebhookTokenAuthnVersion = o.WebHook.Version
   574  		ret.WebhookTokenAuthnCacheTTL = o.WebHook.CacheTTL
   575  		ret.WebhookRetryBackoff = o.WebHook.RetryBackoff
   576  
   577  		if len(o.WebHook.ConfigFile) > 0 && o.WebHook.CacheTTL > 0 {
   578  			if o.TokenSuccessCacheTTL > 0 && o.WebHook.CacheTTL < o.TokenSuccessCacheTTL {
   579  				klog.Warningf("the webhook cache ttl of %s is shorter than the overall cache ttl of %s for successful token authentication attempts.", o.WebHook.CacheTTL, o.TokenSuccessCacheTTL)
   580  			}
   581  			if o.TokenFailureCacheTTL > 0 && o.WebHook.CacheTTL < o.TokenFailureCacheTTL {
   582  				klog.Warningf("the webhook cache ttl of %s is shorter than the overall cache ttl of %s for failed token authentication attempts.", o.WebHook.CacheTTL, o.TokenFailureCacheTTL)
   583  			}
   584  		}
   585  	}
   586  
   587  	return ret, nil
   588  }
   589  
   590  // ApplyTo requires already applied OpenAPIConfig and EgressSelector if present.
   591  // The input context controls the lifecycle of background goroutines started to reload the authentication config file.
   592  func (o *BuiltInAuthenticationOptions) ApplyTo(
   593  	ctx context.Context,
   594  	authInfo *genericapiserver.AuthenticationInfo,
   595  	secureServing *genericapiserver.SecureServingInfo,
   596  	egressSelector *egressselector.EgressSelector,
   597  	openAPIConfig *openapicommon.Config,
   598  	openAPIV3Config *openapicommon.OpenAPIV3Config,
   599  	extclient kubernetes.Interface,
   600  	versionedInformer informers.SharedInformerFactory,
   601  	apiServerID string) error {
   602  	if o == nil {
   603  		return nil
   604  	}
   605  
   606  	if openAPIConfig == nil {
   607  		return errors.New("uninitialized OpenAPIConfig")
   608  	}
   609  
   610  	authenticatorConfig, err := o.ToAuthenticationConfig()
   611  	if err != nil {
   612  		return err
   613  	}
   614  
   615  	if authenticatorConfig.ClientCAContentProvider != nil {
   616  		if err = authInfo.ApplyClientCert(authenticatorConfig.ClientCAContentProvider, secureServing); err != nil {
   617  			return fmt.Errorf("unable to load client CA file: %v", err)
   618  		}
   619  	}
   620  	if authenticatorConfig.RequestHeaderConfig != nil && authenticatorConfig.RequestHeaderConfig.CAContentProvider != nil {
   621  		if err = authInfo.ApplyClientCert(authenticatorConfig.RequestHeaderConfig.CAContentProvider, secureServing); err != nil {
   622  			return fmt.Errorf("unable to load client CA file: %v", err)
   623  		}
   624  	}
   625  
   626  	authInfo.RequestHeaderConfig = authenticatorConfig.RequestHeaderConfig
   627  	authInfo.APIAudiences = o.APIAudiences
   628  	if o.ServiceAccounts != nil && len(o.ServiceAccounts.Issuers) != 0 && len(o.APIAudiences) == 0 {
   629  		authInfo.APIAudiences = authenticator.Audiences(o.ServiceAccounts.Issuers)
   630  	}
   631  
   632  	var nodeLister v1listers.NodeLister
   633  	if utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenNodeBindingValidation) {
   634  		nodeLister = versionedInformer.Core().V1().Nodes().Lister()
   635  	}
   636  	authenticatorConfig.ServiceAccountTokenGetter = serviceaccountcontroller.NewGetterFromClient(
   637  		extclient,
   638  		versionedInformer.Core().V1().Secrets().Lister(),
   639  		versionedInformer.Core().V1().ServiceAccounts().Lister(),
   640  		versionedInformer.Core().V1().Pods().Lister(),
   641  		nodeLister,
   642  	)
   643  	authenticatorConfig.SecretsWriter = extclient.CoreV1()
   644  
   645  	if authenticatorConfig.BootstrapToken {
   646  		authenticatorConfig.BootstrapTokenAuthenticator = bootstrap.NewTokenAuthenticator(
   647  			versionedInformer.Core().V1().Secrets().Lister().Secrets(metav1.NamespaceSystem),
   648  		)
   649  	}
   650  
   651  	if egressSelector != nil {
   652  		egressDialer, err := egressSelector.Lookup(egressselector.ControlPlane.AsNetworkContext())
   653  		if err != nil {
   654  			return err
   655  		}
   656  		authenticatorConfig.CustomDial = egressDialer
   657  	}
   658  
   659  	// var openAPIV3SecuritySchemes spec3.SecuritySchemes
   660  	authenticator, updateAuthenticationConfig, openAPIV2SecurityDefinitions, openAPIV3SecuritySchemes, err := authenticatorConfig.New(ctx)
   661  	if err != nil {
   662  		return err
   663  	}
   664  	authInfo.Authenticator = authenticator
   665  
   666  	if len(o.AuthenticationConfigFile) > 0 {
   667  		authenticationconfigmetrics.RegisterMetrics()
   668  		trackedAuthenticationConfigData := authenticatorConfig.AuthenticationConfigData
   669  		var mu sync.Mutex
   670  		go filesystem.WatchUntil(
   671  			ctx,
   672  			time.Minute,
   673  			o.AuthenticationConfigFile,
   674  			func() {
   675  				// TODO collapse onto shared logic with DynamicEncryptionConfigContent controller
   676  
   677  				mu.Lock()
   678  				defer mu.Unlock()
   679  
   680  				authConfigBytes, err := os.ReadFile(o.AuthenticationConfigFile)
   681  				if err != nil {
   682  					klog.ErrorS(err, "failed to read authentication config file")
   683  					authenticationconfigmetrics.RecordAuthenticationConfigAutomaticReloadFailure(apiServerID)
   684  					// we do not update the tracker here because this error could eventually resolve as we keep retrying
   685  					return
   686  				}
   687  
   688  				authConfigData := string(authConfigBytes)
   689  
   690  				if authConfigData == trackedAuthenticationConfigData {
   691  					return
   692  				}
   693  
   694  				authConfig, err := loadAuthenticationConfigFromData(authConfigBytes)
   695  				if err != nil {
   696  					klog.ErrorS(err, "failed to load authentication config")
   697  					authenticationconfigmetrics.RecordAuthenticationConfigAutomaticReloadFailure(apiServerID)
   698  					// this config is not structurally valid and never will be, update the tracker so we stop retrying
   699  					trackedAuthenticationConfigData = authConfigData
   700  					return
   701  				}
   702  
   703  				if err := apiservervalidation.ValidateAuthenticationConfiguration(authConfig, authenticatorConfig.ServiceAccountIssuers).ToAggregate(); err != nil {
   704  					klog.ErrorS(err, "failed to validate authentication config")
   705  					authenticationconfigmetrics.RecordAuthenticationConfigAutomaticReloadFailure(apiServerID)
   706  					// this config is not semantically valid and never will be, update the tracker so we stop retrying
   707  					trackedAuthenticationConfigData = authConfigData
   708  					return
   709  				}
   710  
   711  				timeoutCtx, timeoutCancel := context.WithTimeout(ctx, UpdateAuthenticationConfigTimeout)
   712  				defer timeoutCancel()
   713  				if err := updateAuthenticationConfig(timeoutCtx, authConfig); err != nil {
   714  					klog.ErrorS(err, "failed to update authentication config")
   715  					authenticationconfigmetrics.RecordAuthenticationConfigAutomaticReloadFailure(apiServerID)
   716  					// we do not update the tracker here because this error could eventually resolve as we keep retrying
   717  					return
   718  				}
   719  
   720  				trackedAuthenticationConfigData = authConfigData
   721  				klog.InfoS("reloaded authentication config")
   722  				authenticationconfigmetrics.RecordAuthenticationConfigAutomaticReloadSuccess(apiServerID)
   723  			},
   724  			func(err error) { klog.ErrorS(err, "watching authentication config file") },
   725  		)
   726  	}
   727  
   728  	openAPIConfig.SecurityDefinitions = openAPIV2SecurityDefinitions
   729  	if openAPIV3Config != nil {
   730  		openAPIV3Config.SecuritySchemes = openAPIV3SecuritySchemes
   731  	}
   732  	return nil
   733  }
   734  
   735  // ApplyAuthorization will conditionally modify the authentication options based on the authorization options
   736  func (o *BuiltInAuthenticationOptions) ApplyAuthorization(authorization *BuiltInAuthorizationOptions) {
   737  	if o == nil || authorization == nil || o.Anonymous == nil {
   738  		return
   739  	}
   740  
   741  	// authorization ModeAlwaysAllow cannot be combined with AnonymousAuth.
   742  	// in such a case the AnonymousAuth is stomped to false and you get a message
   743  	if o.Anonymous.Allow && sets.NewString(authorization.Modes...).Has(authzmodes.ModeAlwaysAllow) {
   744  		klog.Warningf("AnonymousAuth is not allowed with the AlwaysAllow authorizer. Resetting AnonymousAuth to false. You should use a different authorizer")
   745  		o.Anonymous.Allow = false
   746  	}
   747  }
   748  
   749  func (o *BuiltInAuthenticationOptions) validateOIDCOptions() []error {
   750  	var allErrors []error
   751  
   752  	// Existing validation when jwt authenticator is configured with oidc-* flags
   753  	if len(o.AuthenticationConfigFile) == 0 {
   754  		if o.OIDC != nil && o.OIDC.areFlagsConfigured() && (len(o.OIDC.IssuerURL) == 0 || len(o.OIDC.ClientID) == 0) {
   755  			allErrors = append(allErrors, fmt.Errorf("oidc-issuer-url and oidc-client-id must be specified together when any oidc-* flags are set"))
   756  		}
   757  
   758  		return allErrors
   759  	}
   760  
   761  	// New validation when authentication config file is provided
   762  
   763  	// Authentication config file is only supported when the StructuredAuthenticationConfiguration feature is enabled
   764  	if !utilfeature.DefaultFeatureGate.Enabled(genericfeatures.StructuredAuthenticationConfiguration) {
   765  		allErrors = append(allErrors, fmt.Errorf("set --feature-gates=%s=true to use authentication-config file", genericfeatures.StructuredAuthenticationConfiguration))
   766  	}
   767  
   768  	// Authentication config file and oidc-* flags are mutually exclusive
   769  	if o.OIDC != nil && o.OIDC.areFlagsConfigured() {
   770  		allErrors = append(allErrors, fmt.Errorf("authentication-config file and oidc-* flags are mutually exclusive"))
   771  	}
   772  
   773  	return allErrors
   774  }
   775  
   776  var (
   777  	cfgScheme = runtime.NewScheme()
   778  	codecs    = serializer.NewCodecFactory(cfgScheme, serializer.EnableStrict)
   779  )
   780  
   781  func init() {
   782  	install.Install(cfgScheme)
   783  }
   784  
   785  // loadAuthenticationConfig parses the authentication configuration from the given file and returns it and the file's contents.
   786  func loadAuthenticationConfig(configFilePath string) (*apiserver.AuthenticationConfiguration, string, error) {
   787  	data, err := os.ReadFile(configFilePath)
   788  	if err != nil {
   789  		return nil, "", err
   790  	}
   791  
   792  	configuration, err := loadAuthenticationConfigFromData(data)
   793  	if err != nil {
   794  		return nil, "", err
   795  	}
   796  
   797  	return configuration, string(data), nil
   798  }
   799  
   800  func loadAuthenticationConfigFromData(data []byte) (*apiserver.AuthenticationConfiguration, error) {
   801  	if len(data) == 0 {
   802  		return nil, fmt.Errorf("empty config data")
   803  	}
   804  
   805  	decodedObj, err := runtime.Decode(codecs.UniversalDecoder(), data)
   806  	if err != nil {
   807  		return nil, err
   808  	}
   809  	configuration, ok := decodedObj.(*apiserver.AuthenticationConfiguration)
   810  	if !ok {
   811  		return nil, fmt.Errorf("expected AuthenticationConfiguration, got %T", decodedObj)
   812  	}
   813  	if configuration == nil { // sanity check, this should never happen but check just in case since we rely on it
   814  		return nil, fmt.Errorf("expected non-nil AuthenticationConfiguration")
   815  	}
   816  
   817  	return configuration, nil
   818  }
   819  

View as plain text