k8s.io/kubernetes@v1.29.3/pkg/kubeapiserver/options/authentication.go (about)

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