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 }