1
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
75
76 var UpdateAuthenticationConfigTimeout = time.Minute
77
78
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
97 type AnonymousAuthenticationOptions struct {
98 Allow bool
99 }
100
101
102 type BootstrapTokenAuthenticationOptions struct {
103 Enable bool
104 }
105
106
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
119 areFlagsConfigured func() bool
120 }
121
122
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
133 type TokenFileAuthenticationOptions struct {
134 TokenFile string
135 }
136
137
138 type WebHookAuthenticationOptions struct {
139 ConfigFile string
140 Version string
141 CacheTTL time.Duration
142
143
144
145
146 RetryBackoff *wait.Backoff
147 }
148
149
150 func NewBuiltInAuthenticationOptions() *BuiltInAuthenticationOptions {
151 return &BuiltInAuthenticationOptions{
152 TokenSuccessCacheTTL: 10 * time.Second,
153 TokenFailureCacheTTL: 0 * time.Second,
154 }
155 }
156
157
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
171 func (o *BuiltInAuthenticationOptions) WithAnonymous() *BuiltInAuthenticationOptions {
172 o.Anonymous = &AnonymousAuthenticationOptions{Allow: true}
173 return o
174 }
175
176
177 func (o *BuiltInAuthenticationOptions) WithBootstrapToken() *BuiltInAuthenticationOptions {
178 o.BootstrapToken = &BootstrapTokenAuthenticationOptions{}
179 return o
180 }
181
182
183 func (o *BuiltInAuthenticationOptions) WithClientCert() *BuiltInAuthenticationOptions {
184 o.ClientCert = &genericoptions.ClientCertAuthenticationOptions{}
185 return o
186 }
187
188
189 func (o *BuiltInAuthenticationOptions) WithOIDC() *BuiltInAuthenticationOptions {
190 o.OIDC = &OIDCAuthenticationOptions{areFlagsConfigured: func() bool { return false }}
191 return o
192 }
193
194
195 func (o *BuiltInAuthenticationOptions) WithRequestHeader() *BuiltInAuthenticationOptions {
196 o.RequestHeader = &genericoptions.RequestHeaderAuthenticationOptions{}
197 return o
198 }
199
200
201 func (o *BuiltInAuthenticationOptions) WithServiceAccounts() *BuiltInAuthenticationOptions {
202 o.ServiceAccounts = &ServiceAccountAuthenticationOptions{Lookup: true, ExtendExpiration: true}
203 return o
204 }
205
206
207 func (o *BuiltInAuthenticationOptions) WithTokenFile() *BuiltInAuthenticationOptions {
208 o.TokenFile = &TokenFileAuthenticationOptions{}
209 return o
210 }
211
212
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
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
262
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
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
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
444
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
472
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
479
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
486
487
488
489 usernamePrefix = o.OIDC.IssuerURL + "#"
490 }
491 if o.OIDC.UsernamePrefix == "-" {
492
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
591
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
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
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
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
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
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
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
736 func (o *BuiltInAuthenticationOptions) ApplyAuthorization(authorization *BuiltInAuthorizationOptions) {
737 if o == nil || authorization == nil || o.Anonymous == nil {
738 return
739 }
740
741
742
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
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
762
763
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
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
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 {
814 return nil, fmt.Errorf("expected non-nil AuthenticationConfiguration")
815 }
816
817 return configuration, nil
818 }
819
View as plain text