blob: 948478b80ed39791a672fcc8829ad56a6d5c179b [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 headerrequest
18
19import (
20 "crypto/x509"
21 "fmt"
22 "io/ioutil"
23 "net/http"
24 "net/url"
25 "strings"
26
27 "k8s.io/apimachinery/pkg/util/sets"
28 "k8s.io/apiserver/pkg/authentication/authenticator"
29 x509request "k8s.io/apiserver/pkg/authentication/request/x509"
30 "k8s.io/apiserver/pkg/authentication/user"
31 utilcert "k8s.io/client-go/util/cert"
32)
33
34type requestHeaderAuthRequestHandler struct {
35 // nameHeaders are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins.
36 nameHeaders []string
37
38 // groupHeaders are the headers to check (case-insensitively) for group membership. All values of all headers will be added.
39 groupHeaders []string
40
41 // extraHeaderPrefixes are the head prefixes to check (case-insensitively) for filling in
42 // the user.Info.Extra. All values of all matching headers will be added.
43 extraHeaderPrefixes []string
44}
45
46func New(nameHeaders []string, groupHeaders []string, extraHeaderPrefixes []string) (authenticator.Request, error) {
47 trimmedNameHeaders, err := trimHeaders(nameHeaders...)
48 if err != nil {
49 return nil, err
50 }
51 trimmedGroupHeaders, err := trimHeaders(groupHeaders...)
52 if err != nil {
53 return nil, err
54 }
55 trimmedExtraHeaderPrefixes, err := trimHeaders(extraHeaderPrefixes...)
56 if err != nil {
57 return nil, err
58 }
59
60 return &requestHeaderAuthRequestHandler{
61 nameHeaders: trimmedNameHeaders,
62 groupHeaders: trimmedGroupHeaders,
63 extraHeaderPrefixes: trimmedExtraHeaderPrefixes,
64 }, nil
65}
66
67func trimHeaders(headerNames ...string) ([]string, error) {
68 ret := []string{}
69 for _, headerName := range headerNames {
70 trimmedHeader := strings.TrimSpace(headerName)
71 if len(trimmedHeader) == 0 {
72 return nil, fmt.Errorf("empty header %q", headerName)
73 }
74 ret = append(ret, trimmedHeader)
75 }
76
77 return ret, nil
78}
79
80func NewSecure(clientCA string, proxyClientNames []string, nameHeaders []string, groupHeaders []string, extraHeaderPrefixes []string) (authenticator.Request, error) {
81 headerAuthenticator, err := New(nameHeaders, groupHeaders, extraHeaderPrefixes)
82 if err != nil {
83 return nil, err
84 }
85
86 if len(clientCA) == 0 {
87 return nil, fmt.Errorf("missing clientCA file")
88 }
89
90 // Wrap with an x509 verifier
91 caData, err := ioutil.ReadFile(clientCA)
92 if err != nil {
93 return nil, fmt.Errorf("error reading %s: %v", clientCA, err)
94 }
95 opts := x509request.DefaultVerifyOptions()
96 opts.Roots = x509.NewCertPool()
97 certs, err := utilcert.ParseCertsPEM(caData)
98 if err != nil {
99 return nil, fmt.Errorf("error loading certs from %s: %v", clientCA, err)
100 }
101 for _, cert := range certs {
102 opts.Roots.AddCert(cert)
103 }
104
105 return x509request.NewVerifier(opts, headerAuthenticator, sets.NewString(proxyClientNames...)), nil
106}
107
108func (a *requestHeaderAuthRequestHandler) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
109 name := headerValue(req.Header, a.nameHeaders)
110 if len(name) == 0 {
111 return nil, false, nil
112 }
113 groups := allHeaderValues(req.Header, a.groupHeaders)
114 extra := newExtra(req.Header, a.extraHeaderPrefixes)
115
116 // clear headers used for authentication
117 for _, headerName := range a.nameHeaders {
118 req.Header.Del(headerName)
119 }
120 for _, headerName := range a.groupHeaders {
121 req.Header.Del(headerName)
122 }
123 for k := range extra {
124 for _, prefix := range a.extraHeaderPrefixes {
125 req.Header.Del(prefix + k)
126 }
127 }
128
129 return &user.DefaultInfo{
130 Name: name,
131 Groups: groups,
132 Extra: extra,
133 }, true, nil
134}
135
136func headerValue(h http.Header, headerNames []string) string {
137 for _, headerName := range headerNames {
138 headerValue := h.Get(headerName)
139 if len(headerValue) > 0 {
140 return headerValue
141 }
142 }
143 return ""
144}
145
146func allHeaderValues(h http.Header, headerNames []string) []string {
147 ret := []string{}
148 for _, headerName := range headerNames {
149 headerKey := http.CanonicalHeaderKey(headerName)
150 values, ok := h[headerKey]
151 if !ok {
152 continue
153 }
154
155 for _, headerValue := range values {
156 if len(headerValue) > 0 {
157 ret = append(ret, headerValue)
158 }
159 }
160 }
161 return ret
162}
163
164func unescapeExtraKey(encodedKey string) string {
165 key, err := url.PathUnescape(encodedKey) // Decode %-encoded bytes.
166 if err != nil {
167 return encodedKey // Always record extra strings, even if malformed/unencoded.
168 }
169 return key
170}
171
172func newExtra(h http.Header, headerPrefixes []string) map[string][]string {
173 ret := map[string][]string{}
174
175 // we have to iterate over prefixes first in order to have proper ordering inside the value slices
176 for _, prefix := range headerPrefixes {
177 for headerName, vv := range h {
178 if !strings.HasPrefix(strings.ToLower(headerName), strings.ToLower(prefix)) {
179 continue
180 }
181
182 extraKey := unescapeExtraKey(strings.ToLower(headerName[len(prefix):]))
183 ret[extraKey] = append(ret[extraKey], vv...)
184 }
185 }
186
187 return ret
188}