diff --git a/plugin/pkg/auth/authenticator/request/headerrequest/requestheader.go b/plugin/pkg/auth/authenticator/request/headerrequest/requestheader.go new file mode 100644 index 00000000000..4bb1b93c69b --- /dev/null +++ b/plugin/pkg/auth/authenticator/request/headerrequest/requestheader.go @@ -0,0 +1,95 @@ +/* +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 headerrequest + +import ( + "crypto/x509" + "fmt" + "io/ioutil" + "net/http" + "strings" + + "k8s.io/kubernetes/pkg/auth/authenticator" + "k8s.io/kubernetes/pkg/auth/user" + utilcert "k8s.io/kubernetes/pkg/util/cert" + "k8s.io/kubernetes/pkg/util/sets" + x509request "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/x509" +) + +type requestHeaderAuthRequestHandler struct { + nameHeaders []string +} + +func New(nameHeaders []string) (authenticator.Request, error) { + headers := []string{} + for _, headerName := range nameHeaders { + trimmedHeader := strings.TrimSpace(headerName) + if len(trimmedHeader) == 0 { + return nil, fmt.Errorf("empty header %q", headerName) + } + headers = append(headers, trimmedHeader) + } + + return &requestHeaderAuthRequestHandler{nameHeaders: headers}, nil +} + +func NewSecure(clientCA string, proxyClientNames []string, nameHeaders []string) (authenticator.Request, error) { + headerAuthenticator, err := New(nameHeaders) + if err != nil { + return nil, err + } + + if len(clientCA) == 0 { + return nil, fmt.Errorf("missing clientCA file") + } + + // Wrap with an x509 verifier + caData, err := ioutil.ReadFile(clientCA) + if err != nil { + return nil, fmt.Errorf("error reading %s: %v", clientCA, err) + } + opts := x509request.DefaultVerifyOptions() + opts.Roots = x509.NewCertPool() + certs, err := utilcert.ParseCertsPEM(caData) + if err != nil { + return nil, fmt.Errorf("error loading certs from %s: %v", clientCA, err) + } + for _, cert := range certs { + opts.Roots.AddCert(cert) + } + + return x509request.NewVerifier(opts, headerAuthenticator, sets.NewString(proxyClientNames...)), nil +} + +func (a *requestHeaderAuthRequestHandler) AuthenticateRequest(req *http.Request) (user.Info, bool, error) { + name := headerValue(req.Header, a.nameHeaders) + if len(name) == 0 { + return nil, false, nil + } + + return &user.DefaultInfo{Name: name}, true, nil +} + +func headerValue(h http.Header, headerNames []string) string { + for _, headerName := range headerNames { + headerValue := h.Get(headerName) + if len(headerValue) > 0 { + return headerValue + } + } + return "" +} diff --git a/plugin/pkg/auth/authenticator/request/headerrequest/requestheader_test.go b/plugin/pkg/auth/authenticator/request/headerrequest/requestheader_test.go new file mode 100644 index 00000000000..75402ab64e5 --- /dev/null +++ b/plugin/pkg/auth/authenticator/request/headerrequest/requestheader_test.go @@ -0,0 +1,89 @@ +/* +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 headerrequest + +import ( + "net/http" + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/auth/user" +) + +func TestRequestHeader(t *testing.T) { + testcases := map[string]struct { + nameHeaders []string + requestHeaders http.Header + + expectedUser user.Info + expectedOk bool + }{ + "empty": {}, + "no match": { + nameHeaders: []string{"X-Remote-User"}, + }, + "match": { + nameHeaders: []string{"X-Remote-User"}, + requestHeaders: http.Header{"X-Remote-User": {"Bob"}}, + expectedUser: &user.DefaultInfo{Name: "Bob"}, + expectedOk: true, + }, + "exact match": { + nameHeaders: []string{"X-Remote-User"}, + requestHeaders: http.Header{ + "Prefixed-X-Remote-User-With-Suffix": {"Bob"}, + "X-Remote-User-With-Suffix": {"Bob"}, + }, + }, + "first match": { + nameHeaders: []string{ + "X-Remote-User", + "A-Second-X-Remote-User", + "Another-X-Remote-User", + }, + requestHeaders: http.Header{ + "X-Remote-User": {"", "First header, second value"}, + "A-Second-X-Remote-User": {"Second header, first value", "Second header, second value"}, + "Another-X-Remote-User": {"Third header, first value"}}, + expectedUser: &user.DefaultInfo{Name: "Second header, first value"}, + expectedOk: true, + }, + "case-insensitive": { + nameHeaders: []string{"x-REMOTE-user"}, // configured headers can be case-insensitive + requestHeaders: http.Header{"X-Remote-User": {"Bob"}}, // the parsed headers are normalized by the http package + expectedUser: &user.DefaultInfo{Name: "Bob"}, + expectedOk: true, + }, + } + + for k, testcase := range testcases { + auth, err := New(testcase.nameHeaders) + if err != nil { + t.Fatal(err) + } + req := &http.Request{Header: testcase.requestHeaders} + + user, ok, _ := auth.AuthenticateRequest(req) + if testcase.expectedOk != ok { + t.Errorf("%v: expected %v, got %v", k, testcase.expectedOk, ok) + } + if e, a := testcase.expectedUser, user; !reflect.DeepEqual(e, a) { + t.Errorf("%v: expected %#v, got %#v", k, e, a) + + } + } +} diff --git a/test/test_owners.csv b/test/test_owners.csv index 629b0810ed6..617ea14515e 100644 --- a/test/test_owners.csv +++ b/test/test_owners.csv @@ -839,6 +839,7 @@ k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/allow,liggitt,0 k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile,liggitt,0 k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/anonymous,ixdy,1 k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/basicauth,liggitt,0 +k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/headerrequest,deads2k,0 k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/union,liggitt,0 k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/x509,liggitt,0 k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/anytoken,timstclair,1