forked from kubernetes/kubernetes
-
Notifications
You must be signed in to change notification settings - Fork 0
/
authentication.go
403 lines (330 loc) · 14.9 KB
/
authentication.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package options
import (
"fmt"
"net/url"
"strings"
"time"
"github.com/golang/glog"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/authentication/authenticator"
genericapiserver "k8s.io/apiserver/pkg/server"
genericoptions "k8s.io/apiserver/pkg/server/options"
"k8s.io/apiserver/pkg/util/flag"
kubeauthenticator "k8s.io/kubernetes/pkg/kubeapiserver/authenticator"
authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
)
type BuiltInAuthenticationOptions struct {
APIAudiences []string
Anonymous *AnonymousAuthenticationOptions
BootstrapToken *BootstrapTokenAuthenticationOptions
ClientCert *genericoptions.ClientCertAuthenticationOptions
OIDC *OIDCAuthenticationOptions
PasswordFile *PasswordFileAuthenticationOptions
RequestHeader *genericoptions.RequestHeaderAuthenticationOptions
ServiceAccounts *ServiceAccountAuthenticationOptions
TokenFile *TokenFileAuthenticationOptions
WebHook *WebHookAuthenticationOptions
TokenSuccessCacheTTL time.Duration
TokenFailureCacheTTL time.Duration
}
type AnonymousAuthenticationOptions struct {
Allow bool
}
type BootstrapTokenAuthenticationOptions struct {
Enable bool
}
type OIDCAuthenticationOptions struct {
CAFile string
ClientID string
IssuerURL string
UsernameClaim string
UsernamePrefix string
GroupsClaim string
GroupsPrefix string
SigningAlgs []string
RequiredClaims map[string]string
}
type PasswordFileAuthenticationOptions struct {
BasicAuthFile string
}
type ServiceAccountAuthenticationOptions struct {
KeyFiles []string
Lookup bool
Issuer string
MaxExpiration time.Duration
}
type TokenFileAuthenticationOptions struct {
TokenFile string
}
type WebHookAuthenticationOptions struct {
ConfigFile string
CacheTTL time.Duration
}
func NewBuiltInAuthenticationOptions() *BuiltInAuthenticationOptions {
return &BuiltInAuthenticationOptions{
TokenSuccessCacheTTL: 10 * time.Second,
TokenFailureCacheTTL: 0 * time.Second,
}
}
func (s *BuiltInAuthenticationOptions) WithAll() *BuiltInAuthenticationOptions {
return s.
WithAnonymous().
WithBootstrapToken().
WithClientCert().
WithOIDC().
WithPasswordFile().
WithRequestHeader().
WithServiceAccounts().
WithTokenFile().
WithWebHook()
}
func (s *BuiltInAuthenticationOptions) WithAnonymous() *BuiltInAuthenticationOptions {
s.Anonymous = &AnonymousAuthenticationOptions{Allow: true}
return s
}
func (s *BuiltInAuthenticationOptions) WithBootstrapToken() *BuiltInAuthenticationOptions {
s.BootstrapToken = &BootstrapTokenAuthenticationOptions{}
return s
}
func (s *BuiltInAuthenticationOptions) WithClientCert() *BuiltInAuthenticationOptions {
s.ClientCert = &genericoptions.ClientCertAuthenticationOptions{}
return s
}
func (s *BuiltInAuthenticationOptions) WithOIDC() *BuiltInAuthenticationOptions {
s.OIDC = &OIDCAuthenticationOptions{}
return s
}
func (s *BuiltInAuthenticationOptions) WithPasswordFile() *BuiltInAuthenticationOptions {
s.PasswordFile = &PasswordFileAuthenticationOptions{}
return s
}
func (s *BuiltInAuthenticationOptions) WithRequestHeader() *BuiltInAuthenticationOptions {
s.RequestHeader = &genericoptions.RequestHeaderAuthenticationOptions{}
return s
}
func (s *BuiltInAuthenticationOptions) WithServiceAccounts() *BuiltInAuthenticationOptions {
s.ServiceAccounts = &ServiceAccountAuthenticationOptions{Lookup: true}
return s
}
func (s *BuiltInAuthenticationOptions) WithTokenFile() *BuiltInAuthenticationOptions {
s.TokenFile = &TokenFileAuthenticationOptions{}
return s
}
func (s *BuiltInAuthenticationOptions) WithWebHook() *BuiltInAuthenticationOptions {
s.WebHook = &WebHookAuthenticationOptions{
CacheTTL: 2 * time.Minute,
}
return s
}
// Validate checks invalid config combination
func (s *BuiltInAuthenticationOptions) Validate() []error {
allErrors := []error{}
if s.OIDC != nil && (len(s.OIDC.IssuerURL) > 0) != (len(s.OIDC.ClientID) > 0) {
allErrors = append(allErrors, fmt.Errorf("oidc-issuer-url and oidc-client-id should be specified together"))
}
if s.ServiceAccounts != nil && len(s.ServiceAccounts.Issuer) > 0 && strings.Contains(s.ServiceAccounts.Issuer, ":") {
if _, err := url.Parse(s.ServiceAccounts.Issuer); err != nil {
allErrors = append(allErrors, fmt.Errorf("service-account-issuer contained a ':' but was not a valid URL: %v", err))
}
}
return allErrors
}
func (s *BuiltInAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringSliceVar(&s.APIAudiences, "api-audiences", s.APIAudiences, ""+
"Identifiers of the API. The service account token authenticator will validate that "+
"tokens used against the API are bound to at least one of these audiences. If the "+
"--service-account-issuer flag is configured and this flag is not, this field "+
"defaults to a single element list containing the issuer URL .")
if s.Anonymous != nil {
fs.BoolVar(&s.Anonymous.Allow, "anonymous-auth", s.Anonymous.Allow, ""+
"Enables anonymous requests to the secure port of the API server. "+
"Requests that are not rejected by another authentication method are treated as anonymous requests. "+
"Anonymous requests have a username of system:anonymous, and a group name of system:unauthenticated.")
}
if s.BootstrapToken != nil {
fs.BoolVar(&s.BootstrapToken.Enable, "enable-bootstrap-token-auth", s.BootstrapToken.Enable, ""+
"Enable to allow secrets of type 'bootstrap.kubernetes.io/token' in the 'kube-system' "+
"namespace to be used for TLS bootstrapping authentication.")
}
if s.ClientCert != nil {
s.ClientCert.AddFlags(fs)
}
if s.OIDC != nil {
fs.StringVar(&s.OIDC.IssuerURL, "oidc-issuer-url", s.OIDC.IssuerURL, ""+
"The URL of the OpenID issuer, only HTTPS scheme will be accepted. "+
"If set, it will be used to verify the OIDC JSON Web Token (JWT).")
fs.StringVar(&s.OIDC.ClientID, "oidc-client-id", s.OIDC.ClientID,
"The client ID for the OpenID Connect client, must be set if oidc-issuer-url is set.")
fs.StringVar(&s.OIDC.CAFile, "oidc-ca-file", s.OIDC.CAFile, ""+
"If set, the OpenID server's certificate will be verified by one of the authorities "+
"in the oidc-ca-file, otherwise the host's root CA set will be used.")
fs.StringVar(&s.OIDC.UsernameClaim, "oidc-username-claim", "sub", ""+
"The OpenID claim to use as the user name. Note that claims other than the default ('sub') "+
"is not guaranteed to be unique and immutable. This flag is experimental, please see "+
"the authentication documentation for further details.")
fs.StringVar(&s.OIDC.UsernamePrefix, "oidc-username-prefix", "", ""+
"If provided, all usernames will be prefixed with this value. If not provided, "+
"username claims other than 'email' are prefixed by the issuer URL to avoid "+
"clashes. To skip any prefixing, provide the value '-'.")
fs.StringVar(&s.OIDC.GroupsClaim, "oidc-groups-claim", "", ""+
"If provided, the name of a custom OpenID Connect claim for specifying user groups. "+
"The claim value is expected to be a string or array of strings. This flag is experimental, "+
"please see the authentication documentation for further details.")
fs.StringVar(&s.OIDC.GroupsPrefix, "oidc-groups-prefix", "", ""+
"If provided, all groups will be prefixed with this value to prevent conflicts with "+
"other authentication strategies.")
fs.StringSliceVar(&s.OIDC.SigningAlgs, "oidc-signing-algs", []string{"RS256"}, ""+
"Comma-separated list of allowed JOSE asymmetric signing algorithms. JWTs with a "+
"'alg' header value not in this list will be rejected. "+
"Values are defined by RFC 7518 https://tools.ietf.org/html/rfc7518#section-3.1.")
fs.Var(flag.NewMapStringStringNoSplit(&s.OIDC.RequiredClaims), "oidc-required-claim", ""+
"A key=value pair that describes a required claim in the ID Token. "+
"If set, the claim is verified to be present in the ID Token with a matching value. "+
"Repeat this flag to specify multiple claims.")
}
if s.PasswordFile != nil {
fs.StringVar(&s.PasswordFile.BasicAuthFile, "basic-auth-file", s.PasswordFile.BasicAuthFile, ""+
"If set, the file that will be used to admit requests to the secure port of the API server "+
"via http basic authentication.")
}
if s.RequestHeader != nil {
s.RequestHeader.AddFlags(fs)
}
if s.ServiceAccounts != nil {
fs.StringArrayVar(&s.ServiceAccounts.KeyFiles, "service-account-key-file", s.ServiceAccounts.KeyFiles, ""+
"File containing PEM-encoded x509 RSA or ECDSA private or public keys, used to verify "+
"ServiceAccount tokens. The specified file can contain multiple keys, and the flag can "+
"be specified multiple times with different files. If unspecified, "+
"--tls-private-key-file is used. Must be specified when "+
"--service-account-signing-key is provided")
fs.BoolVar(&s.ServiceAccounts.Lookup, "service-account-lookup", s.ServiceAccounts.Lookup,
"If true, validate ServiceAccount tokens exist in etcd as part of authentication.")
fs.StringVar(&s.ServiceAccounts.Issuer, "service-account-issuer", s.ServiceAccounts.Issuer, ""+
"Identifier of the service account token issuer. The issuer will assert this identifier "+
"in \"iss\" claim of issued tokens. This value is a string or URI.")
// Deprecated in 1.13
fs.StringSliceVar(&s.APIAudiences, "service-account-api-audiences", s.APIAudiences, ""+
"Identifiers of the API. The service account token authenticator will validate that "+
"tokens used against the API are bound to at least one of these audiences.")
fs.MarkDeprecated("service-account-api-audiences", "Use --api-audiences")
fs.DurationVar(&s.ServiceAccounts.MaxExpiration, "service-account-max-token-expiration", s.ServiceAccounts.MaxExpiration, ""+
"The maximum validity duration of a token created by the service account token issuer. If an otherwise valid "+
"TokenRequest with a validity duration larger than this value is requested, a token will be issued with a validity duration of this value.")
}
if s.TokenFile != nil {
fs.StringVar(&s.TokenFile.TokenFile, "token-auth-file", s.TokenFile.TokenFile, ""+
"If set, the file that will be used to secure the secure port of the API server "+
"via token authentication.")
}
if s.WebHook != nil {
fs.StringVar(&s.WebHook.ConfigFile, "authentication-token-webhook-config-file", s.WebHook.ConfigFile, ""+
"File with webhook configuration for token authentication in kubeconfig format. "+
"The API server will query the remote service to determine authentication for bearer tokens.")
fs.DurationVar(&s.WebHook.CacheTTL, "authentication-token-webhook-cache-ttl", s.WebHook.CacheTTL,
"The duration to cache responses from the webhook token authenticator.")
}
}
func (s *BuiltInAuthenticationOptions) ToAuthenticationConfig() kubeauthenticator.Config {
ret := kubeauthenticator.Config{
TokenSuccessCacheTTL: s.TokenSuccessCacheTTL,
TokenFailureCacheTTL: s.TokenFailureCacheTTL,
}
if s.Anonymous != nil {
ret.Anonymous = s.Anonymous.Allow
}
if s.BootstrapToken != nil {
ret.BootstrapToken = s.BootstrapToken.Enable
}
if s.ClientCert != nil {
ret.ClientCAFile = s.ClientCert.ClientCA
}
if s.OIDC != nil {
ret.OIDCCAFile = s.OIDC.CAFile
ret.OIDCClientID = s.OIDC.ClientID
ret.OIDCGroupsClaim = s.OIDC.GroupsClaim
ret.OIDCGroupsPrefix = s.OIDC.GroupsPrefix
ret.OIDCIssuerURL = s.OIDC.IssuerURL
ret.OIDCUsernameClaim = s.OIDC.UsernameClaim
ret.OIDCUsernamePrefix = s.OIDC.UsernamePrefix
ret.OIDCSigningAlgs = s.OIDC.SigningAlgs
ret.OIDCRequiredClaims = s.OIDC.RequiredClaims
}
if s.PasswordFile != nil {
ret.BasicAuthFile = s.PasswordFile.BasicAuthFile
}
if s.RequestHeader != nil {
ret.RequestHeaderConfig = s.RequestHeader.ToAuthenticationRequestHeaderConfig()
}
ret.APIAudiences = s.APIAudiences
if s.ServiceAccounts != nil {
if s.ServiceAccounts.Issuer != "" && len(s.APIAudiences) == 0 {
ret.APIAudiences = authenticator.Audiences{s.ServiceAccounts.Issuer}
}
ret.ServiceAccountKeyFiles = s.ServiceAccounts.KeyFiles
ret.ServiceAccountIssuer = s.ServiceAccounts.Issuer
ret.ServiceAccountLookup = s.ServiceAccounts.Lookup
}
if s.TokenFile != nil {
ret.TokenAuthFile = s.TokenFile.TokenFile
}
if s.WebHook != nil {
ret.WebhookTokenAuthnConfigFile = s.WebHook.ConfigFile
ret.WebhookTokenAuthnCacheTTL = s.WebHook.CacheTTL
if len(s.WebHook.ConfigFile) > 0 && s.WebHook.CacheTTL > 0 {
if s.TokenSuccessCacheTTL > 0 && s.WebHook.CacheTTL < s.TokenSuccessCacheTTL {
glog.Warningf("the webhook cache ttl of %s is shorter than the overall cache ttl of %s for successful token authentication attempts.", s.WebHook.CacheTTL, s.TokenSuccessCacheTTL)
}
if s.TokenFailureCacheTTL > 0 && s.WebHook.CacheTTL < s.TokenFailureCacheTTL {
glog.Warningf("the webhook cache ttl of %s is shorter than the overall cache ttl of %s for failed token authentication attempts.", s.WebHook.CacheTTL, s.TokenFailureCacheTTL)
}
}
}
return ret
}
func (o *BuiltInAuthenticationOptions) ApplyTo(c *genericapiserver.Config) error {
if o == nil {
return nil
}
var err error
if o.ClientCert != nil {
if err = c.Authentication.ApplyClientCert(o.ClientCert.ClientCA, c.SecureServing); err != nil {
return fmt.Errorf("unable to load client CA file: %v", err)
}
}
if o.RequestHeader != nil {
if err = c.Authentication.ApplyClientCert(o.RequestHeader.ClientCAFile, c.SecureServing); err != nil {
return fmt.Errorf("unable to load client CA file: %v", err)
}
}
c.Authentication.SupportsBasicAuth = o.PasswordFile != nil && len(o.PasswordFile.BasicAuthFile) > 0
c.Authentication.APIAudiences = o.APIAudiences
if o.ServiceAccounts != nil && o.ServiceAccounts.Issuer != "" && len(o.APIAudiences) == 0 {
c.Authentication.APIAudiences = authenticator.Audiences{o.ServiceAccounts.Issuer}
}
return nil
}
// ApplyAuthorization will conditionally modify the authentication options based on the authorization options
func (o *BuiltInAuthenticationOptions) ApplyAuthorization(authorization *BuiltInAuthorizationOptions) {
if o == nil || authorization == nil || o.Anonymous == nil {
return
}
// authorization ModeAlwaysAllow cannot be combined with AnonymousAuth.
// in such a case the AnonymousAuth is stomped to false and you get a message
if o.Anonymous.Allow && sets.NewString(authorization.Modes...).Has(authzmodes.ModeAlwaysAllow) {
glog.Warningf("AnonymousAuth is not allowed with the AlwaysAllow authorizer. Resetting AnonymousAuth to false. You should use a different authorizer")
o.Anonymous.Allow = false
}
}