k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/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 "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 }