@@ -17,10 +17,13 @@ limitations under the License.
17
17
package authenticator
18
18
19
19
import (
20
+ "context"
20
21
"errors"
21
22
"fmt"
23
+ "sync/atomic"
22
24
"time"
23
25
26
+ utilerrors "k8s.io/apimachinery/pkg/util/errors"
24
27
utilnet "k8s.io/apimachinery/pkg/util/net"
25
28
"k8s.io/apimachinery/pkg/util/wait"
26
29
"k8s.io/apiserver/pkg/apis/apiserver"
@@ -57,6 +60,7 @@ type Config struct {
57
60
58
61
TokenAuthFile string
59
62
AuthenticationConfig * apiserver.AuthenticationConfiguration
63
+ AuthenticationConfigData string
60
64
OIDCSigningAlgs []string
61
65
ServiceAccountKeyFiles []string
62
66
ServiceAccountLookup bool
@@ -90,7 +94,7 @@ type Config struct {
90
94
91
95
// New returns an authenticator.Request or an error that supports the standard
92
96
// Kubernetes authentication mechanisms.
93
- func (config Config ) New () (authenticator.Request , * spec.SecurityDefinitions , spec3.SecuritySchemes , error ) {
97
+ func (config Config ) New (serverLifecycle context. Context ) (authenticator.Request , func (context. Context , * apiserver. AuthenticationConfiguration ) error , * spec.SecurityDefinitions , spec3.SecuritySchemes , error ) {
94
98
var authenticators []authenticator.Request
95
99
var tokenAuthenticators []authenticator.Token
96
100
securityDefinitionsV2 := spec.SecurityDefinitions {}
@@ -119,21 +123,21 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, sp
119
123
if len (config .TokenAuthFile ) > 0 {
120
124
tokenAuth , err := newAuthenticatorFromTokenFile (config .TokenAuthFile )
121
125
if err != nil {
122
- return nil , nil , nil , err
126
+ return nil , nil , nil , nil , err
123
127
}
124
128
tokenAuthenticators = append (tokenAuthenticators , authenticator .WrapAudienceAgnosticToken (config .APIAudiences , tokenAuth ))
125
129
}
126
130
if len (config .ServiceAccountKeyFiles ) > 0 {
127
131
serviceAccountAuth , err := newLegacyServiceAccountAuthenticator (config .ServiceAccountKeyFiles , config .ServiceAccountLookup , config .APIAudiences , config .ServiceAccountTokenGetter , config .SecretsWriter )
128
132
if err != nil {
129
- return nil , nil , nil , err
133
+ return nil , nil , nil , nil , err
130
134
}
131
135
tokenAuthenticators = append (tokenAuthenticators , serviceAccountAuth )
132
136
}
133
137
if len (config .ServiceAccountIssuers ) > 0 {
134
138
serviceAccountAuth , err := newServiceAccountAuthenticator (config .ServiceAccountIssuers , config .ServiceAccountKeyFiles , config .APIAudiences , config .ServiceAccountTokenGetter )
135
139
if err != nil {
136
- return nil , nil , nil , err
140
+ return nil , nil , nil , nil , err
137
141
}
138
142
tokenAuthenticators = append (tokenAuthenticators , serviceAccountAuth )
139
143
}
@@ -148,33 +152,33 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, sp
148
152
// cache misses for all requests using the other. While the service account plugin
149
153
// simply returns an error, the OpenID Connect plugin may query the provider to
150
154
// update the keys, causing performance hits.
155
+ var updateAuthenticationConfig func (context.Context , * apiserver.AuthenticationConfiguration ) error
151
156
if config .AuthenticationConfig != nil {
152
- for _ , jwtAuthenticator := range config .AuthenticationConfig .JWT {
153
- var oidcCAContent oidc.CAContentProvider
154
- if len (jwtAuthenticator .Issuer .CertificateAuthority ) > 0 {
155
- var oidcCAError error
156
- oidcCAContent , oidcCAError = dynamiccertificates .NewStaticCAContent ("oidc-authenticator" , []byte (jwtAuthenticator .Issuer .CertificateAuthority ))
157
- if oidcCAError != nil {
158
- return nil , nil , nil , oidcCAError
159
- }
160
- }
161
- oidcAuth , err := oidc .New (oidc.Options {
162
- JWTAuthenticator : jwtAuthenticator ,
163
- CAContentProvider : oidcCAContent ,
164
- SupportedSigningAlgs : config .OIDCSigningAlgs ,
165
- DisallowedIssuers : config .ServiceAccountIssuers ,
166
- })
167
- if err != nil {
168
- return nil , nil , nil , err
169
- }
170
- tokenAuthenticators = append (tokenAuthenticators , authenticator .WrapAudienceAgnosticToken (config .APIAudiences , oidcAuth ))
157
+ initialJWTAuthenticator , err := newJWTAuthenticator (serverLifecycle , config .AuthenticationConfig , config .OIDCSigningAlgs , config .APIAudiences , config .ServiceAccountIssuers )
158
+ if err != nil {
159
+ return nil , nil , nil , nil , err
171
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
+ )
172
176
}
173
177
174
178
if len (config .WebhookTokenAuthnConfigFile ) > 0 {
175
179
webhookTokenAuth , err := newWebhookTokenAuthenticator (config )
176
180
if err != nil {
177
- return nil , nil , nil , err
181
+ return nil , nil , nil , nil , err
178
182
}
179
183
180
184
tokenAuthenticators = append (tokenAuthenticators , webhookTokenAuth )
@@ -209,9 +213,9 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, sp
209
213
210
214
if len (authenticators ) == 0 {
211
215
if config .Anonymous {
212
- return anonymous .NewAuthenticator (), & securityDefinitionsV2 , securitySchemesV3 , nil
216
+ return anonymous .NewAuthenticator (), nil , & securityDefinitionsV2 , securitySchemesV3 , nil
213
217
}
214
- return nil , & securityDefinitionsV2 , securitySchemesV3 , nil
218
+ return nil , nil , & securityDefinitionsV2 , securitySchemesV3 , nil
215
219
}
216
220
217
221
authenticator := union .New (authenticators ... )
@@ -224,7 +228,97 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, sp
224
228
authenticator = union .NewFailOnError (authenticator , anonymous .NewAuthenticator ())
225
229
}
226
230
227
- return authenticator , & securityDefinitionsV2 , securitySchemesV3 , nil
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
+ // TODO remove this CAContentProvider indirection
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 ... )), // this handles the empty jwtAuthenticators slice case correctly
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
+ // the input ctx controls the timeout for updateAuthenticationConfig to return, not the lifetime of the constructed authenticators.
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 }) // filters out nil errors
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
+ // TODO maybe track requests so we know when this is safe to do
318
+ oldJWTAuthenticator .cancel ()
319
+ }()
320
+
321
+ return nil
228
322
}
229
323
230
324
// IsValidServiceAccountKeyFile returns true if a valid public RSA key can be read from the given file
0 commit comments