blob: 2ec5024de9c50e1305a538977c985e805cf69b0b [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
2Copyright 2016 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package options
18
19import (
20 "encoding/json"
21 "fmt"
22 "io/ioutil"
23 "time"
24
25 "github.com/golang/glog"
26 "github.com/spf13/pflag"
27
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 "k8s.io/apiserver/pkg/authentication/authenticatorfactory"
30 "k8s.io/apiserver/pkg/server"
31 authenticationclient "k8s.io/client-go/kubernetes/typed/authentication/v1beta1"
32 coreclient "k8s.io/client-go/kubernetes/typed/core/v1"
33 "k8s.io/client-go/rest"
34 "k8s.io/client-go/tools/clientcmd"
35 openapicommon "k8s.io/kube-openapi/pkg/common"
36)
37
38type RequestHeaderAuthenticationOptions struct {
39 UsernameHeaders []string
40 GroupHeaders []string
41 ExtraHeaderPrefixes []string
42 ClientCAFile string
43 AllowedNames []string
44}
45
46func (s *RequestHeaderAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
47 if s == nil {
48 return
49 }
50
51 fs.StringSliceVar(&s.UsernameHeaders, "requestheader-username-headers", s.UsernameHeaders, ""+
52 "List of request headers to inspect for usernames. X-Remote-User is common.")
53
54 fs.StringSliceVar(&s.GroupHeaders, "requestheader-group-headers", s.GroupHeaders, ""+
55 "List of request headers to inspect for groups. X-Remote-Group is suggested.")
56
57 fs.StringSliceVar(&s.ExtraHeaderPrefixes, "requestheader-extra-headers-prefix", s.ExtraHeaderPrefixes, ""+
58 "List of request header prefixes to inspect. X-Remote-Extra- is suggested.")
59
60 fs.StringVar(&s.ClientCAFile, "requestheader-client-ca-file", s.ClientCAFile, ""+
61 "Root certificate bundle to use to verify client certificates on incoming requests "+
62 "before trusting usernames in headers specified by --requestheader-username-headers. "+
63 "WARNING: generally do not depend on authorization being already done for incoming requests.")
64
65 fs.StringSliceVar(&s.AllowedNames, "requestheader-allowed-names", s.AllowedNames, ""+
66 "List of client certificate common names to allow to provide usernames in headers "+
67 "specified by --requestheader-username-headers. If empty, any client certificate validated "+
68 "by the authorities in --requestheader-client-ca-file is allowed.")
69}
70
71// ToAuthenticationRequestHeaderConfig returns a RequestHeaderConfig config object for these options
72// if necessary, nil otherwise.
73func (s *RequestHeaderAuthenticationOptions) ToAuthenticationRequestHeaderConfig() *authenticatorfactory.RequestHeaderConfig {
74 if len(s.ClientCAFile) == 0 {
75 return nil
76 }
77
78 return &authenticatorfactory.RequestHeaderConfig{
79 UsernameHeaders: s.UsernameHeaders,
80 GroupHeaders: s.GroupHeaders,
81 ExtraHeaderPrefixes: s.ExtraHeaderPrefixes,
82 ClientCA: s.ClientCAFile,
83 AllowedClientNames: s.AllowedNames,
84 }
85}
86
87type ClientCertAuthenticationOptions struct {
88 // ClientCA is the certificate bundle for all the signers that you'll recognize for incoming client certificates
89 ClientCA string
90}
91
92func (s *ClientCertAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
93 fs.StringVar(&s.ClientCA, "client-ca-file", s.ClientCA, ""+
94 "If set, any request presenting a client certificate signed by one of "+
95 "the authorities in the client-ca-file is authenticated with an identity "+
96 "corresponding to the CommonName of the client certificate.")
97}
98
99// DelegatingAuthenticationOptions provides an easy way for composing API servers to delegate their authentication to
100// the root kube API server. The API federator will act as
101// a front proxy and direction connections will be able to delegate to the core kube API server
102type DelegatingAuthenticationOptions struct {
103 // RemoteKubeConfigFile is the file to use to connect to a "normal" kube API server which hosts the
104 // TokenAccessReview.authentication.k8s.io endpoint for checking tokens.
105 RemoteKubeConfigFile string
106
107 // CacheTTL is the length of time that a token authentication answer will be cached.
108 CacheTTL time.Duration
109
110 ClientCert ClientCertAuthenticationOptions
111 RequestHeader RequestHeaderAuthenticationOptions
112
113 SkipInClusterLookup bool
114}
115
116func NewDelegatingAuthenticationOptions() *DelegatingAuthenticationOptions {
117 return &DelegatingAuthenticationOptions{
118 // very low for responsiveness, but high enough to handle storms
119 CacheTTL: 10 * time.Second,
120 ClientCert: ClientCertAuthenticationOptions{},
121 RequestHeader: RequestHeaderAuthenticationOptions{
122 UsernameHeaders: []string{"x-remote-user"},
123 GroupHeaders: []string{"x-remote-group"},
124 ExtraHeaderPrefixes: []string{"x-remote-extra-"},
125 },
126 }
127}
128
129func (s *DelegatingAuthenticationOptions) Validate() []error {
130 allErrors := []error{}
131 return allErrors
132}
133
134func (s *DelegatingAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
135 if s == nil {
136 return
137 }
138
139 fs.StringVar(&s.RemoteKubeConfigFile, "authentication-kubeconfig", s.RemoteKubeConfigFile, ""+
140 "kubeconfig file pointing at the 'core' kubernetes server with enough rights to create "+
141 "tokenaccessreviews.authentication.k8s.io.")
142
143 fs.DurationVar(&s.CacheTTL, "authentication-token-webhook-cache-ttl", s.CacheTTL,
144 "The duration to cache responses from the webhook token authenticator.")
145
146 s.ClientCert.AddFlags(fs)
147 s.RequestHeader.AddFlags(fs)
148
149 fs.BoolVar(&s.SkipInClusterLookup, "authentication-skip-lookup", s.SkipInClusterLookup, ""+
150 "If false, the authentication-kubeconfig will be used to lookup missing authentication "+
151 "configuration from the cluster.")
152
153}
154
155func (s *DelegatingAuthenticationOptions) ApplyTo(c *server.AuthenticationInfo, servingInfo *server.SecureServingInfo, openAPIConfig *openapicommon.Config) error {
156 if s == nil {
157 c.Authenticator = nil
158 return nil
159 }
160
161 clientCA, err := s.getClientCA()
162 if err != nil {
163 if _, ignorable := err.(ignorableError); !ignorable {
164 return err
165 } else {
166 glog.Warning(err)
167 }
168 }
169 if err = c.ApplyClientCert(clientCA.ClientCA, servingInfo); err != nil {
170 return fmt.Errorf("unable to load client CA file: %v", err)
171 }
172
173 requestHeader, err := s.getRequestHeader()
174 if err != nil {
175 return err
176 }
177 if err = c.ApplyClientCert(requestHeader.ClientCAFile, servingInfo); err != nil {
178 return fmt.Errorf("unable to load client CA file: %v", err)
179 }
180
181 cfg, err := s.ToAuthenticationConfig()
182 if err != nil {
183 return err
184 }
185 authenticator, securityDefinitions, err := cfg.New()
186 if err != nil {
187 return err
188 }
189
190 c.Authenticator = authenticator
191 if openAPIConfig != nil {
192 openAPIConfig.SecurityDefinitions = securityDefinitions
193 }
194 c.SupportsBasicAuth = false
195
196 return nil
197}
198
199func (s *DelegatingAuthenticationOptions) ToAuthenticationConfig() (authenticatorfactory.DelegatingAuthenticatorConfig, error) {
200 tokenClient, err := s.newTokenAccessReview()
201 if err != nil {
202 return authenticatorfactory.DelegatingAuthenticatorConfig{}, err
203 }
204
205 clientCA, err := s.getClientCA()
206 if err != nil {
207 if _, ignorable := err.(ignorableError); !ignorable {
208 return authenticatorfactory.DelegatingAuthenticatorConfig{}, err
209 } else {
210 glog.Warning(err)
211 }
212 }
213 requestHeader, err := s.getRequestHeader()
214 if err != nil {
215 return authenticatorfactory.DelegatingAuthenticatorConfig{}, err
216 }
217
218 ret := authenticatorfactory.DelegatingAuthenticatorConfig{
219 Anonymous: true,
220 TokenAccessReviewClient: tokenClient,
221 CacheTTL: s.CacheTTL,
222 ClientCAFile: clientCA.ClientCA,
223 RequestHeaderConfig: requestHeader.ToAuthenticationRequestHeaderConfig(),
224 }
225 return ret, nil
226}
227
228const (
229 authenticationConfigMapNamespace = metav1.NamespaceSystem
230 // authenticationConfigMapName is the name of ConfigMap in the kube-system namespace holding the root certificate
231 // bundle to use to verify client certificates on incoming requests before trusting usernames in headers specified
232 // by --requestheader-username-headers. This is created in the cluster by the kube-apiserver.
233 // "WARNING: generally do not depend on authorization being already done for incoming requests.")
234 authenticationConfigMapName = "extension-apiserver-authentication"
235 authenticationRoleName = "extension-apiserver-authentication-reader"
236)
237
238func (s *DelegatingAuthenticationOptions) getClientCA() (*ClientCertAuthenticationOptions, error) {
239 if len(s.ClientCert.ClientCA) > 0 || s.SkipInClusterLookup {
240 return &s.ClientCert, nil
241 }
242
243 incluster, err := s.lookupInClusterClientCA()
244 if err != nil {
245 glog.Warningf("Unable to get configmap/%s in %s. Usually fixed by "+
246 "'kubectl create rolebinding -n %s ROLE_NAME --role=%s --serviceaccount=YOUR_NS:YOUR_SA'",
247 authenticationConfigMapName, authenticationConfigMapNamespace, authenticationConfigMapNamespace, authenticationRoleName)
248 return nil, err
249 }
250 if incluster == nil {
251 return &s.ClientCert, ignorableError{fmt.Errorf("cluster doesn't provide client-ca-file in configmap/%s in %s, so client certificate authentication to extension api-server won't work.", authenticationConfigMapName, authenticationConfigMapNamespace)}
252 }
253 return incluster, nil
254}
255
256func (s *DelegatingAuthenticationOptions) getRequestHeader() (*RequestHeaderAuthenticationOptions, error) {
257 if len(s.RequestHeader.ClientCAFile) > 0 || s.SkipInClusterLookup {
258 return &s.RequestHeader, nil
259 }
260
261 incluster, err := s.lookupInClusterRequestHeader()
262 if err != nil {
263 glog.Warningf("Unable to get configmap/%s in %s. Usually fixed by "+
264 "'kubectl create rolebinding -n %s ROLE_NAME --role=%s --serviceaccount=YOUR_NS:YOUR_SA'",
265 authenticationConfigMapName, authenticationConfigMapNamespace, authenticationConfigMapNamespace, authenticationRoleName)
266 return nil, err
267 }
268 if incluster == nil {
269 return nil, fmt.Errorf("cluster doesn't provide requestheader-client-ca-file")
270 }
271 return incluster, nil
272}
273
274func (s *DelegatingAuthenticationOptions) lookupInClusterClientCA() (*ClientCertAuthenticationOptions, error) {
275 clientConfig, err := s.getClientConfig()
276 if err != nil {
277 return nil, err
278 }
279 client, err := coreclient.NewForConfig(clientConfig)
280 if err != nil {
281 return nil, err
282 }
283
284 authConfigMap, err := client.ConfigMaps(authenticationConfigMapNamespace).Get(authenticationConfigMapName, metav1.GetOptions{})
285 if err != nil {
286 return nil, err
287 }
288
289 clientCA, ok := authConfigMap.Data["client-ca-file"]
290 if !ok {
291 return nil, nil
292 }
293
294 f, err := ioutil.TempFile("", "client-ca-file")
295 if err != nil {
296 return nil, err
297 }
298 if err := ioutil.WriteFile(f.Name(), []byte(clientCA), 0600); err != nil {
299 return nil, err
300 }
301 return &ClientCertAuthenticationOptions{ClientCA: f.Name()}, nil
302}
303
304func (s *DelegatingAuthenticationOptions) lookupInClusterRequestHeader() (*RequestHeaderAuthenticationOptions, error) {
305 clientConfig, err := s.getClientConfig()
306 if err != nil {
307 return nil, err
308 }
309 client, err := coreclient.NewForConfig(clientConfig)
310 if err != nil {
311 return nil, err
312 }
313
314 authConfigMap, err := client.ConfigMaps(authenticationConfigMapNamespace).Get(authenticationConfigMapName, metav1.GetOptions{})
315 if err != nil {
316 return nil, err
317 }
318
319 requestHeaderCA, ok := authConfigMap.Data["requestheader-client-ca-file"]
320 if !ok {
321 return nil, nil
322 }
323
324 f, err := ioutil.TempFile("", "requestheader-client-ca-file")
325 if err != nil {
326 return nil, err
327 }
328 if err := ioutil.WriteFile(f.Name(), []byte(requestHeaderCA), 0600); err != nil {
329 return nil, err
330 }
331 usernameHeaders, err := deserializeStrings(authConfigMap.Data["requestheader-username-headers"])
332 if err != nil {
333 return nil, err
334 }
335 groupHeaders, err := deserializeStrings(authConfigMap.Data["requestheader-group-headers"])
336 if err != nil {
337 return nil, err
338 }
339 extraHeaderPrefixes, err := deserializeStrings(authConfigMap.Data["requestheader-extra-headers-prefix"])
340 if err != nil {
341 return nil, err
342 }
343 allowedNames, err := deserializeStrings(authConfigMap.Data["requestheader-allowed-names"])
344 if err != nil {
345 return nil, err
346 }
347
348 return &RequestHeaderAuthenticationOptions{
349 UsernameHeaders: usernameHeaders,
350 GroupHeaders: groupHeaders,
351 ExtraHeaderPrefixes: extraHeaderPrefixes,
352 ClientCAFile: f.Name(),
353 AllowedNames: allowedNames,
354 }, nil
355}
356
357func deserializeStrings(in string) ([]string, error) {
358 if len(in) == 0 {
359 return nil, nil
360 }
361 var ret []string
362 if err := json.Unmarshal([]byte(in), &ret); err != nil {
363 return nil, err
364 }
365 return ret, nil
366}
367
368func (s *DelegatingAuthenticationOptions) getClientConfig() (*rest.Config, error) {
369 var clientConfig *rest.Config
370 var err error
371 if len(s.RemoteKubeConfigFile) > 0 {
372 loadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: s.RemoteKubeConfigFile}
373 loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
374
375 clientConfig, err = loader.ClientConfig()
376
377 } else {
378 // without the remote kubeconfig file, try to use the in-cluster config. Most addon API servers will
379 // use this path
380 clientConfig, err = rest.InClusterConfig()
381 }
382 if err != nil {
383 return nil, err
384 }
385
386 // set high qps/burst limits since this will effectively limit API server responsiveness
387 clientConfig.QPS = 200
388 clientConfig.Burst = 400
389
390 return clientConfig, nil
391}
392
393func (s *DelegatingAuthenticationOptions) newTokenAccessReview() (authenticationclient.TokenReviewInterface, error) {
394 clientConfig, err := s.getClientConfig()
395 if err != nil {
396 return nil, err
397 }
398 client, err := authenticationclient.NewForConfig(clientConfig)
399 if err != nil {
400 return nil, err
401 }
402
403 return client.TokenReviews(), nil
404}
405
406type ignorableError struct{ error }