1
16
17 package authenticator
18
19 import (
20 "context"
21 "errors"
22 "fmt"
23 "sync/atomic"
24 "time"
25
26 utilerrors "k8s.io/apimachinery/pkg/util/errors"
27 utilnet "k8s.io/apimachinery/pkg/util/net"
28 "k8s.io/apimachinery/pkg/util/wait"
29 "k8s.io/apiserver/pkg/apis/apiserver"
30 "k8s.io/apiserver/pkg/authentication/authenticator"
31 "k8s.io/apiserver/pkg/authentication/authenticatorfactory"
32 "k8s.io/apiserver/pkg/authentication/group"
33 "k8s.io/apiserver/pkg/authentication/request/anonymous"
34 "k8s.io/apiserver/pkg/authentication/request/bearertoken"
35 "k8s.io/apiserver/pkg/authentication/request/headerrequest"
36 "k8s.io/apiserver/pkg/authentication/request/union"
37 "k8s.io/apiserver/pkg/authentication/request/websocket"
38 "k8s.io/apiserver/pkg/authentication/request/x509"
39 tokencache "k8s.io/apiserver/pkg/authentication/token/cache"
40 "k8s.io/apiserver/pkg/authentication/token/tokenfile"
41 tokenunion "k8s.io/apiserver/pkg/authentication/token/union"
42 "k8s.io/apiserver/pkg/server/dynamiccertificates"
43 webhookutil "k8s.io/apiserver/pkg/util/webhook"
44 "k8s.io/apiserver/plugin/pkg/authenticator/token/oidc"
45 "k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
46 typedv1core "k8s.io/client-go/kubernetes/typed/core/v1"
47 "k8s.io/kube-openapi/pkg/spec3"
48 "k8s.io/kube-openapi/pkg/validation/spec"
49
50
51 _ "k8s.io/client-go/plugin/pkg/client/auth"
52 "k8s.io/client-go/util/keyutil"
53 "k8s.io/kubernetes/pkg/serviceaccount"
54 )
55
56
57 type Config struct {
58 Anonymous bool
59 BootstrapToken bool
60
61 TokenAuthFile string
62 AuthenticationConfig *apiserver.AuthenticationConfiguration
63 AuthenticationConfigData string
64 OIDCSigningAlgs []string
65 ServiceAccountKeyFiles []string
66 ServiceAccountLookup bool
67 ServiceAccountIssuers []string
68 APIAudiences authenticator.Audiences
69 WebhookTokenAuthnConfigFile string
70 WebhookTokenAuthnVersion string
71 WebhookTokenAuthnCacheTTL time.Duration
72
73
74
75 WebhookRetryBackoff *wait.Backoff
76
77 TokenSuccessCacheTTL time.Duration
78 TokenFailureCacheTTL time.Duration
79
80 RequestHeaderConfig *authenticatorfactory.RequestHeaderConfig
81
82
83 ServiceAccountTokenGetter serviceaccount.ServiceAccountTokenGetter
84 SecretsWriter typedv1core.SecretsGetter
85 BootstrapTokenAuthenticator authenticator.Token
86
87
88
89 ClientCAContentProvider dynamiccertificates.CAContentProvider
90
91
92 CustomDial utilnet.DialFunc
93 }
94
95
96
97 func (config Config) New(serverLifecycle context.Context) (authenticator.Request, func(context.Context, *apiserver.AuthenticationConfiguration) error, *spec.SecurityDefinitions, spec3.SecuritySchemes, error) {
98 var authenticators []authenticator.Request
99 var tokenAuthenticators []authenticator.Token
100 securityDefinitionsV2 := spec.SecurityDefinitions{}
101 securitySchemesV3 := spec3.SecuritySchemes{}
102
103
104
105 if config.RequestHeaderConfig != nil {
106 requestHeaderAuthenticator := headerrequest.NewDynamicVerifyOptionsSecure(
107 config.RequestHeaderConfig.CAContentProvider.VerifyOptions,
108 config.RequestHeaderConfig.AllowedClientNames,
109 config.RequestHeaderConfig.UsernameHeaders,
110 config.RequestHeaderConfig.GroupHeaders,
111 config.RequestHeaderConfig.ExtraHeaderPrefixes,
112 )
113 authenticators = append(authenticators, authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, requestHeaderAuthenticator))
114 }
115
116
117 if config.ClientCAContentProvider != nil {
118 certAuth := x509.NewDynamic(config.ClientCAContentProvider.VerifyOptions, x509.CommonNameUserConversion)
119 authenticators = append(authenticators, certAuth)
120 }
121
122
123 if len(config.TokenAuthFile) > 0 {
124 tokenAuth, err := newAuthenticatorFromTokenFile(config.TokenAuthFile)
125 if err != nil {
126 return nil, nil, nil, nil, err
127 }
128 tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, tokenAuth))
129 }
130 if len(config.ServiceAccountKeyFiles) > 0 {
131 serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.APIAudiences, config.ServiceAccountTokenGetter, config.SecretsWriter)
132 if err != nil {
133 return nil, nil, nil, nil, err
134 }
135 tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
136 }
137 if len(config.ServiceAccountIssuers) > 0 {
138 serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuers, config.ServiceAccountKeyFiles, config.APIAudiences, config.ServiceAccountTokenGetter)
139 if err != nil {
140 return nil, nil, nil, nil, err
141 }
142 tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
143 }
144
145 if config.BootstrapToken && config.BootstrapTokenAuthenticator != nil {
146 tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, config.BootstrapTokenAuthenticator))
147 }
148
149
150
151
152
153
154
155 var updateAuthenticationConfig func(context.Context, *apiserver.AuthenticationConfiguration) error
156 if config.AuthenticationConfig != nil {
157 initialJWTAuthenticator, err := newJWTAuthenticator(serverLifecycle, config.AuthenticationConfig, config.OIDCSigningAlgs, config.APIAudiences, config.ServiceAccountIssuers)
158 if err != nil {
159 return nil, nil, nil, nil, err
160 }
161
162 jwtAuthenticatorPtr := &atomic.Pointer[jwtAuthenticatorWithCancel]{}
163 jwtAuthenticatorPtr.Store(initialJWTAuthenticator)
164
165 updateAuthenticationConfig = (&authenticationConfigUpdater{
166 serverLifecycle: serverLifecycle,
167 config: config,
168 jwtAuthenticatorPtr: jwtAuthenticatorPtr,
169 }).updateAuthenticationConfig
170
171 tokenAuthenticators = append(tokenAuthenticators,
172 authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
173 return jwtAuthenticatorPtr.Load().jwtAuthenticator.AuthenticateToken(ctx, token)
174 }),
175 )
176 }
177
178 if len(config.WebhookTokenAuthnConfigFile) > 0 {
179 webhookTokenAuth, err := newWebhookTokenAuthenticator(config)
180 if err != nil {
181 return nil, nil, nil, nil, err
182 }
183
184 tokenAuthenticators = append(tokenAuthenticators, webhookTokenAuth)
185 }
186
187 if len(tokenAuthenticators) > 0 {
188
189 tokenAuth := tokenunion.New(tokenAuthenticators...)
190
191 if config.TokenSuccessCacheTTL > 0 || config.TokenFailureCacheTTL > 0 {
192 tokenAuth = tokencache.New(tokenAuth, true, config.TokenSuccessCacheTTL, config.TokenFailureCacheTTL)
193 }
194 authenticators = append(authenticators, bearertoken.New(tokenAuth), websocket.NewProtocolAuthenticator(tokenAuth))
195
196 securityDefinitionsV2["BearerToken"] = &spec.SecurityScheme{
197 SecuritySchemeProps: spec.SecuritySchemeProps{
198 Type: "apiKey",
199 Name: "authorization",
200 In: "header",
201 Description: "Bearer Token authentication",
202 },
203 }
204 securitySchemesV3["BearerToken"] = &spec3.SecurityScheme{
205 SecuritySchemeProps: spec3.SecuritySchemeProps{
206 Type: "apiKey",
207 Name: "authorization",
208 In: "header",
209 Description: "Bearer Token authentication",
210 },
211 }
212 }
213
214 if len(authenticators) == 0 {
215 if config.Anonymous {
216 return anonymous.NewAuthenticator(), nil, &securityDefinitionsV2, securitySchemesV3, nil
217 }
218 return nil, nil, &securityDefinitionsV2, securitySchemesV3, nil
219 }
220
221 authenticator := union.New(authenticators...)
222
223 authenticator = group.NewAuthenticatedGroupAdder(authenticator)
224
225 if config.Anonymous {
226
227
228 authenticator = union.NewFailOnError(authenticator, anonymous.NewAuthenticator())
229 }
230
231 return authenticator, updateAuthenticationConfig, &securityDefinitionsV2, securitySchemesV3, nil
232 }
233
234 type jwtAuthenticatorWithCancel struct {
235 jwtAuthenticator authenticator.Token
236 healthCheck func() error
237 cancel func()
238 }
239
240 func newJWTAuthenticator(serverLifecycle context.Context, config *apiserver.AuthenticationConfiguration, oidcSigningAlgs []string, apiAudiences authenticator.Audiences, disallowedIssuers []string) (_ *jwtAuthenticatorWithCancel, buildErr error) {
241 ctx, cancel := context.WithCancel(serverLifecycle)
242
243 defer func() {
244 if buildErr != nil {
245 cancel()
246 }
247 }()
248 var jwtAuthenticators []authenticator.Token
249 var healthChecks []func() error
250 for _, jwtAuthenticator := range config.JWT {
251
252 var oidcCAContent oidc.CAContentProvider
253 if len(jwtAuthenticator.Issuer.CertificateAuthority) > 0 {
254 var oidcCAError error
255 oidcCAContent, oidcCAError = dynamiccertificates.NewStaticCAContent("oidc-authenticator", []byte(jwtAuthenticator.Issuer.CertificateAuthority))
256 if oidcCAError != nil {
257 return nil, oidcCAError
258 }
259 }
260 oidcAuth, err := oidc.New(ctx, oidc.Options{
261 JWTAuthenticator: jwtAuthenticator,
262 CAContentProvider: oidcCAContent,
263 SupportedSigningAlgs: oidcSigningAlgs,
264 DisallowedIssuers: disallowedIssuers,
265 })
266 if err != nil {
267 return nil, err
268 }
269 jwtAuthenticators = append(jwtAuthenticators, oidcAuth)
270 healthChecks = append(healthChecks, oidcAuth.HealthCheck)
271 }
272 return &jwtAuthenticatorWithCancel{
273 jwtAuthenticator: authenticator.WrapAudienceAgnosticToken(apiAudiences, tokenunion.NewFailOnError(jwtAuthenticators...)),
274 healthCheck: func() error {
275 var errs []error
276 for _, check := range healthChecks {
277 if err := check(); err != nil {
278 errs = append(errs, err)
279 }
280 }
281 return utilerrors.NewAggregate(errs)
282 },
283 cancel: cancel,
284 }, nil
285 }
286
287 type authenticationConfigUpdater struct {
288 serverLifecycle context.Context
289 config Config
290 jwtAuthenticatorPtr *atomic.Pointer[jwtAuthenticatorWithCancel]
291 }
292
293
294 func (c *authenticationConfigUpdater) updateAuthenticationConfig(ctx context.Context, authConfig *apiserver.AuthenticationConfiguration) error {
295 updatedJWTAuthenticator, err := newJWTAuthenticator(c.serverLifecycle, authConfig, c.config.OIDCSigningAlgs, c.config.APIAudiences, c.config.ServiceAccountIssuers)
296 if err != nil {
297 return err
298 }
299
300 var lastErr error
301 if waitErr := wait.PollUntilContextCancel(ctx, 10*time.Second, true, func(_ context.Context) (done bool, err error) {
302 lastErr = updatedJWTAuthenticator.healthCheck()
303 return lastErr == nil, nil
304 }); lastErr != nil || waitErr != nil {
305 updatedJWTAuthenticator.cancel()
306 return utilerrors.NewAggregate([]error{lastErr, waitErr})
307 }
308
309 oldJWTAuthenticator := c.jwtAuthenticatorPtr.Swap(updatedJWTAuthenticator)
310 go func() {
311 t := time.NewTimer(time.Minute)
312 defer t.Stop()
313 select {
314 case <-c.serverLifecycle.Done():
315 case <-t.C:
316 }
317
318 oldJWTAuthenticator.cancel()
319 }()
320
321 return nil
322 }
323
324
325 func IsValidServiceAccountKeyFile(file string) bool {
326 _, err := keyutil.PublicKeysFromFile(file)
327 return err == nil
328 }
329
330
331 func newAuthenticatorFromTokenFile(tokenAuthFile string) (authenticator.Token, error) {
332 tokenAuthenticator, err := tokenfile.NewCSV(tokenAuthFile)
333 if err != nil {
334 return nil, err
335 }
336
337 return tokenAuthenticator, nil
338 }
339
340
341 func newLegacyServiceAccountAuthenticator(keyfiles []string, lookup bool, apiAudiences authenticator.Audiences, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter, secretsWriter typedv1core.SecretsGetter) (authenticator.Token, error) {
342 allPublicKeys := []interface{}{}
343 for _, keyfile := range keyfiles {
344 publicKeys, err := keyutil.PublicKeysFromFile(keyfile)
345 if err != nil {
346 return nil, err
347 }
348 allPublicKeys = append(allPublicKeys, publicKeys...)
349 }
350 validator, err := serviceaccount.NewLegacyValidator(lookup, serviceAccountGetter, secretsWriter)
351 if err != nil {
352 return nil, fmt.Errorf("while creating legacy validator, err: %w", err)
353 }
354
355 tokenAuthenticator := serviceaccount.JWTTokenAuthenticator([]string{serviceaccount.LegacyIssuer}, allPublicKeys, apiAudiences, validator)
356 return tokenAuthenticator, nil
357 }
358
359
360 func newServiceAccountAuthenticator(issuers []string, keyfiles []string, apiAudiences authenticator.Audiences, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
361 allPublicKeys := []interface{}{}
362 for _, keyfile := range keyfiles {
363 publicKeys, err := keyutil.PublicKeysFromFile(keyfile)
364 if err != nil {
365 return nil, err
366 }
367 allPublicKeys = append(allPublicKeys, publicKeys...)
368 }
369
370 tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(issuers, allPublicKeys, apiAudiences, serviceaccount.NewValidator(serviceAccountGetter))
371 return tokenAuthenticator, nil
372 }
373
374 func newWebhookTokenAuthenticator(config Config) (authenticator.Token, error) {
375 if config.WebhookRetryBackoff == nil {
376 return nil, errors.New("retry backoff parameters for authentication webhook has not been specified")
377 }
378
379 clientConfig, err := webhookutil.LoadKubeconfig(config.WebhookTokenAuthnConfigFile, config.CustomDial)
380 if err != nil {
381 return nil, err
382 }
383 webhookTokenAuthenticator, err := webhook.New(clientConfig, config.WebhookTokenAuthnVersion, config.APIAudiences, *config.WebhookRetryBackoff)
384 if err != nil {
385 return nil, err
386 }
387
388 return tokencache.New(webhookTokenAuthenticator, false, config.WebhookTokenAuthnCacheTTL, config.WebhookTokenAuthnCacheTTL), nil
389 }
390
View as plain text