Browse code

Add ability to specify allowed CNs for RequestHeader proxy client cert

Jordan Liggitt authored on 2016/04/11 12:34:32
Showing 9 changed files
... ...
@@ -3,11 +3,14 @@ package x509request
3 3
 import (
4 4
 	"crypto/x509"
5 5
 	"crypto/x509/pkix"
6
+	"fmt"
6 7
 	"net/http"
7 8
 
9
+	"github.com/golang/glog"
8 10
 	"github.com/openshift/origin/pkg/auth/authenticator"
9 11
 	"k8s.io/kubernetes/pkg/auth/user"
10 12
 	kerrors "k8s.io/kubernetes/pkg/util/errors"
13
+	"k8s.io/kubernetes/pkg/util/sets"
11 14
 )
12 15
 
13 16
 // UserConversion defines an interface for extracting user info from a client certificate chain
... ...
@@ -68,10 +71,14 @@ func (a *Authenticator) AuthenticateRequest(req *http.Request) (user.Info, bool,
68 68
 type Verifier struct {
69 69
 	opts x509.VerifyOptions
70 70
 	auth authenticator.Request
71
+
72
+	// allowedCommonNames contains the common names which a verified certificate is allowed to have.
73
+	// If empty, all verified certificates are allowed.
74
+	allowedCommonNames sets.String
71 75
 }
72 76
 
73
-func NewVerifier(opts x509.VerifyOptions, auth authenticator.Request) authenticator.Request {
74
-	return &Verifier{opts, auth}
77
+func NewVerifier(opts x509.VerifyOptions, auth authenticator.Request, allowedCommonNames sets.String) authenticator.Request {
78
+	return &Verifier{opts, auth, allowedCommonNames}
75 79
 }
76 80
 
77 81
 // AuthenticateRequest verifies the presented client certificates, then delegates to the wrapped auth
... ...
@@ -82,8 +89,11 @@ func (a *Verifier) AuthenticateRequest(req *http.Request) (user.Info, bool, erro
82 82
 
83 83
 	var errlist []error
84 84
 	for _, cert := range req.TLS.PeerCertificates {
85
-		_, err := cert.Verify(a.opts)
86
-		if err != nil {
85
+		if _, err := cert.Verify(a.opts); err != nil {
86
+			errlist = append(errlist, err)
87
+			continue
88
+		}
89
+		if err := a.verifySubject(cert.Subject); err != nil {
87 90
 			errlist = append(errlist, err)
88 91
 			continue
89 92
 		}
... ...
@@ -92,6 +102,19 @@ func (a *Verifier) AuthenticateRequest(req *http.Request) (user.Info, bool, erro
92 92
 	return nil, false, kerrors.NewAggregate(errlist)
93 93
 }
94 94
 
95
+func (a *Verifier) verifySubject(subject pkix.Name) error {
96
+	// No CN restrictions
97
+	if len(a.allowedCommonNames) == 0 {
98
+		return nil
99
+	}
100
+	// Enforce CN restrictions
101
+	if a.allowedCommonNames.Has(subject.CommonName) {
102
+		return nil
103
+	}
104
+	glog.Warningf("x509: subject with cn=%s is not in the allowed list: %v", subject.CommonName, a.allowedCommonNames.List())
105
+	return fmt.Errorf("x509: subject with cn=%s is not allowed", subject.CommonName)
106
+}
107
+
95 108
 // DefaultVerifyOptions returns VerifyOptions that use the system root certificates, current time,
96 109
 // and requires certificates to be valid for client auth (x509.ExtKeyUsageClientAuth)
97 110
 func DefaultVerifyOptions() x509.VerifyOptions {
... ...
@@ -11,6 +11,7 @@ import (
11 11
 
12 12
 	"github.com/openshift/origin/pkg/auth/authenticator"
13 13
 	"k8s.io/kubernetes/pkg/auth/user"
14
+	"k8s.io/kubernetes/pkg/util/sets"
14 15
 )
15 16
 
16 17
 const (
... ...
@@ -536,6 +537,8 @@ func TestX509Verifier(t *testing.T) {
536 536
 
537 537
 		Opts x509.VerifyOptions
538 538
 
539
+		AllowedCNs sets.String
540
+
539 541
 		ExpectOK  bool
540 542
 		ExpectErr bool
541 543
 	}{
... ...
@@ -579,6 +582,22 @@ func TestX509Verifier(t *testing.T) {
579 579
 			ExpectOK:  true,
580 580
 			ExpectErr: false,
581 581
 		},
582
+		"valid client cert with wrong CN": {
583
+			Opts:       getDefaultVerifyOptions(t),
584
+			AllowedCNs: sets.NewString("foo", "bar"),
585
+			Certs:      getCerts(t, clientCNCert),
586
+
587
+			ExpectOK:  false,
588
+			ExpectErr: true,
589
+		},
590
+		"valid client cert with right CN": {
591
+			Opts:       getDefaultVerifyOptions(t),
592
+			AllowedCNs: sets.NewString("client_cn"),
593
+			Certs:      getCerts(t, clientCNCert),
594
+
595
+			ExpectOK:  true,
596
+			ExpectErr: false,
597
+		},
582 598
 
583 599
 		"future cert": {
584 600
 			Opts: x509.VerifyOptions{
... ...
@@ -614,7 +633,7 @@ func TestX509Verifier(t *testing.T) {
614 614
 			return &user.DefaultInfo{Name: "innerauth"}, true, nil
615 615
 		})
616 616
 
617
-		a := NewVerifier(testCase.Opts, auth)
617
+		a := NewVerifier(testCase.Opts, auth, testCase.AllowedCNs)
618 618
 
619 619
 		user, ok, err := a.AuthenticateRequest(req)
620 620
 
... ...
@@ -742,6 +742,9 @@ type RequestHeaderIdentityProvider struct {
742 742
 
743 743
 	// ClientCA is a file with the trusted signer certs.  If empty, no request verification is done, and any direct request to the OAuth server can impersonate any identity from this provider, merely by setting a request header.
744 744
 	ClientCA string
745
+	// ClientCommonNames is an optional list of common names to require a match from. If empty, any client certificate validated against the clientCA bundle is considered authoritative.
746
+	ClientCommonNames []string
747
+
745 748
 	// Headers is the set of headers to check for identity information
746 749
 	Headers []string
747 750
 	// PreferredUsernameHeaders is the set of headers to check for the preferred username
... ...
@@ -617,6 +617,7 @@ var map_RequestHeaderIdentityProvider = map[string]string{
617 617
 	"loginURL":                 "LoginURL is a URL to redirect unauthenticated /authorize requests to Unauthenticated requests from OAuth clients which expect interactive logins will be redirected here ${url} is replaced with the current URL, escaped to be safe in a query parameter\n  https://www.example.com/sso-login?then=${url}\n${query} is replaced with the current query string\n  https://www.example.com/auth-proxy/oauth/authorize?${query}",
618 618
 	"challengeURL":             "ChallengeURL is a URL to redirect unauthenticated /authorize requests to Unauthenticated requests from OAuth clients which expect WWW-Authenticate challenges will be redirected here ${url} is replaced with the current URL, escaped to be safe in a query parameter\n  https://www.example.com/sso-login?then=${url}\n${query} is replaced with the current query string\n  https://www.example.com/auth-proxy/oauth/authorize?${query}",
619 619
 	"clientCA":                 "ClientCA is a file with the trusted signer certs.  If empty, no request verification is done, and any direct request to the OAuth server can impersonate any identity from this provider, merely by setting a request header.",
620
+	"clientCommonNames":        "ClientCommonNames is an optional list of common names to require a match from. If empty, any client certificate validated against the clientCA bundle is considered authoritative.",
620 621
 	"headers":                  "Headers is the set of headers to check for identity information",
621 622
 	"preferredUsernameHeaders": "PreferredUsernameHeaders is the set of headers to check for the preferred username",
622 623
 	"nameHeaders":              "NameHeaders is the set of headers to check for the display name",
... ...
@@ -732,6 +732,9 @@ type RequestHeaderIdentityProvider struct {
732 732
 
733 733
 	// ClientCA is a file with the trusted signer certs.  If empty, no request verification is done, and any direct request to the OAuth server can impersonate any identity from this provider, merely by setting a request header.
734 734
 	ClientCA string `json:"clientCA"`
735
+	// ClientCommonNames is an optional list of common names to require a match from. If empty, any client certificate validated against the clientCA bundle is considered authoritative.
736
+	ClientCommonNames []string `json:"clientCommonNames"`
737
+
735 738
 	// Headers is the set of headers to check for identity information
736 739
 	Headers []string `json:"headers"`
737 740
 	// PreferredUsernameHeaders is the set of headers to check for the preferred username
... ...
@@ -272,6 +272,7 @@ oauthConfig:
272 272
       apiVersion: v1
273 273
       challengeURL: ""
274 274
       clientCA: ""
275
+      clientCommonNames: null
275 276
       emailHeaders: null
276 277
       headers: null
277 278
       kind: RequestHeaderIdentityProvider
... ...
@@ -235,7 +235,10 @@ func ValidateRequestHeaderIdentityProvider(provider *api.RequestHeaderIdentityPr
235 235
 
236 236
 	if len(provider.ClientCA) > 0 {
237 237
 		validationResults.AddErrors(ValidateFile(provider.ClientCA, fieldPath.Child("provider", "clientCA"))...)
238
+	} else if len(provider.ClientCommonNames) > 0 {
239
+		validationResults.AddErrors(field.Invalid(fieldPath.Child("provider", "clientCommonNames"), provider.ClientCommonNames, "clientCA must be specified in order to use clientCommonNames"))
238 240
 	}
241
+
239 242
 	if len(provider.Headers) == 0 {
240 243
 		validationResults.AddErrors(field.Required(fieldPath.Child("provider", "headers"), ""))
241 244
 	}
... ...
@@ -683,7 +683,7 @@ func (c *AuthConfig) getAuthenticationRequestHandler() (authenticator.Request, e
683 683
 						return nil, fmt.Errorf("Error loading certs from %s: %v", provider.ClientCA, err)
684 684
 					}
685 685
 
686
-					authRequestHandler = x509request.NewVerifier(opts, authRequestHandler)
686
+					authRequestHandler = x509request.NewVerifier(opts, authRequestHandler, sets.NewString(provider.ClientCommonNames...))
687 687
 				}
688 688
 				authRequestHandlers = append(authRequestHandlers, authRequestHandler)
689 689
 
... ...
@@ -23,80 +23,9 @@ import (
23 23
 	testserver "github.com/openshift/origin/test/util/server"
24 24
 )
25 25
 
26
-var (
27
-	rootCACert = []byte(`-----BEGIN CERTIFICATE-----
28
-MIIC7DCCAdagAwIBAgIBATALBgkqhkiG9w0BAQswKDEmMCQGA1UEAwwdMTAuMTMu
29
-MTI5LjE0OTo4NDQzQDE0MjU2NzUyNzQwIBcNMTUwMzA2MjA1NDM0WhgPMjExNTAy
30
-MTAyMDU0MzVaMCgxJjAkBgNVBAMMHTEwLjEzLjEyOS4xNDk6ODQ0M0AxNDI1Njc1
31
-Mjc0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuOnLZ0PgeEKnbV7D
32
-93g6fcllMh6ngCnQpEoaWSHTWjPbv/qDU/jRQU2l/KHOkMXKsbNiasRT6ZIWlUFc
33
-W/Jgd1Tz7zjh+pgJHLEtKdWVPwP/8ruUhQotrb1E/q1g21wqczPxfb+Z9s6+AnkF
34
-FLooBCCRa8wpC+TtcAaT7/yEJfN6IUhcT9XFmLzKTPz76UXBHMN+KDeK0k0u77a9
35
-vj+eAedB6Xg9lfpvIclvjgy6cvQ9oavYTJ8Q5mYZdIdspmSzFjAyZUylgpEIpPkN
36
-e8dcqiA0hc2Mq/pwwn/F3i4va/NO7+Od9gRkAtvuvCUASXuCmon6pRYAZEImevRt
37
-GbRlkQIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAKQwDwYDVR0TAQH/BAUwAwEB/zAL
38
-BgkqhkiG9w0BAQsDggEBAD30//8aJkPLtJ0G6/0oa+zjKBZH04PyWCjTsgaDCHVm
39
-z/AntWxKR5fc+z/NXfnhV8M8/zb4ZGHp+jczozvcXZxUgftlUFNxV7sY8NXdJNrs
40
-t+oFURLIibIjxN0vlz7py16RxXy693t6PzfQB/69ZB/AI3VfyOdJ1cvaV/kOce21
41
-Kp/jmVz5DUhQI60zcUOE4at81emo3uYK7Pz9iil2Wu2lK4+1uP4LdZRRLEUXWqNb
42
-VmAB7OAhfJ2/x/BsPIvbI1aGp7DjjQgaeBwXD/mW8AUJHHbdvWUYz1yNyQ2XDWZm
43
-X2kxcf0iGTwuqufTmw7EcDc/dWIdJ6bsB007/M9bz3g=
44
-`)
45
-
46
-	clientCert = []byte(`-----BEGIN CERTIFICATE-----
47
-MIIC+DCCAeKgAwIBAgIBBTALBgkqhkiG9w0BAQswKDEmMCQGA1UEAwwdMTAuMTMu
48
-MTI5LjE0OTo4NDQzQDE0MjU2NzUyNzQwIBcNMTUwMzA2MjA1NDM2WhgPMjExNTAy
49
-MTAyMDU0MzdaMCIxIDAeBgNVBAMTF3N5c3RlbTpvcGVuc2hpZnQtY2xpZW50MIIB
50
-IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwbkwwrV4j3xqmUhyKErAzfAI
51
-UX5atGGJHt+oRmZ3BzeAl6CpGLGLSiYso4j5JmLo0qpvQroSw66oOoVMw2851nhI
52
-OZHo7aGvJ9elgmwa7ghDg4DN3TUe8y9Ex+JUDnAAK5dY0DV8UK7Aa2SAxIlSMGIu
53
-VuUcjwlC9w37D3VxDFoa6XO+SUBiRUJyjiDlLNUegyV60jimxVTZbTb8r90lbGc8
54
-iB5j6py5ZCF/UMRY5LEuIum/7dKvH2A03q2n3Y58qcAhWIp6lP9DeJe3CMvuK64C
55
-BwvT9jm9TioRGZskqfV3mLYyhxp1q2FKK03umQ5KKNYqvppFUYVKdzXgFjxfMQID
56
-AQABozUwMzAOBgNVHQ8BAf8EBAMCAKAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYD
57
-VR0TAQH/BAIwADALBgkqhkiG9w0BAQsDggEBACY2Lu5kl9a2cBi3+WB/oo7iG82S
58
-9IdIaABDLFWCGp4e0PEGAfROlNcCrhmEizkhPXeDNrjmDKeShnu1E/8RgwBtDrym
59
-v9WQBa/HI3ZbO3hDdR2pNo6c+y3MqDJHO8/4l7hV5DYY9ypfB85mZQ7uxKaFawqs
60
-GqLZNJWjpG6T9yUDaj2fO+etXb5dPTZiSytw1Z4l2GDGElLRjVS4k2aP0Lo/BLXG
61
-1nUatU3KcCEeb1ifghFjDLESo8mUwfBl9v1vO75rIFRDfoPFMqIHGhRmP+fbnCI8
62
-fWh90BcEhK0TheTyEHBPtpKKiYz5BWyNlkCuTmhygaRCa9SQWl2nRi2XVGY=
63
-`)
64
-
65
-	clientKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
66
-MIIEpAIBAAKCAQEAwbkwwrV4j3xqmUhyKErAzfAIUX5atGGJHt+oRmZ3BzeAl6Cp
67
-GLGLSiYso4j5JmLo0qpvQroSw66oOoVMw2851nhIOZHo7aGvJ9elgmwa7ghDg4DN
68
-3TUe8y9Ex+JUDnAAK5dY0DV8UK7Aa2SAxIlSMGIuVuUcjwlC9w37D3VxDFoa6XO+
69
-SUBiRUJyjiDlLNUegyV60jimxVTZbTb8r90lbGc8iB5j6py5ZCF/UMRY5LEuIum/
70
-7dKvH2A03q2n3Y58qcAhWIp6lP9DeJe3CMvuK64CBwvT9jm9TioRGZskqfV3mLYy
71
-hxp1q2FKK03umQ5KKNYqvppFUYVKdzXgFjxfMQIDAQABAoIBADg/olXWxUO8V2Nc
72
-crEaS3NAT9oBuyqG636IaF7Qn5z7052zK4Yc/xmvjeSJ//XSYFHS5O1WA97Hltcv
73
-H0PbxspsMGRu5lghSy9hYRBGfWdCBQBo5N1m8C6iOfFj2Q48HQCLOGF0Nj1jEEHe
74
-c7kdOj0MNPJMIgeyI7yCVbR+YC26dfxiaIfRtyzsScsNX/pP1AH9lEd9c6reMvms
75
-UxjplUkYjk4gbngmKJjd2MD8dc8XqR5V+Oq1uwOQG/EZhSBlyxwRVMFwd5/opb2Q
76
-JqMZvd458MQ2C2RSZALXDYYmCMbXU76Crg7N3+y34d+uwkIufUhFfTR7BuUkvlzt
77
-5vb+WTECgYEAyVCSxvTbB9y3IQpsRlJeBwVnWNHclZft4g9PtqQgA6VaWqSoYvFz
78
-t2m2/L3O39zEgalM6HesVT8EdiIWvp08eHYvFTb9jaqxHIbwdrzxtvBn5SJ6CjCX
79
-xA+uWv3AbH+H2t/ZCiPAKebcOfefmce6/8cKrNKFXs5KojR6tpM2Lo0CgYEA9li3
80
-JDTRbCGLsuyVhDT3l4przzJC0DwU1j8zBJfUrBtuPMhDISHHvq9opSoTQsRXz9EH
81
-ruQe3/XCvE/y988E3oVh+ikmBX01xCsIUB0jUhItVQ7GSacY0+UgZmH6Zw7xJjO5
82
-zwaYGnejOxHIs0XmeajbhYl+bAGEym+iV682rDUCgYEAn0ox2VtFNCNgg7RLmBj0
83
-bXnJHG5xq6xbfdO/rzSOYFQl+jLvSdrjRO1Q7QsC9f8pPa9IO2j14z3Jue+fL5Qa
84
-lPZuqsqoNcAqA/iBrHI0kBwJGTT+e7GXZHtD6pt99luyk20rvuoq0vzopLVag8OW
85
-I2zK9ZReE3YHd/EuZ+hzpsECgYBiTXSHliwboidE9vOTFi/W4P20aLIQtmj6Na3+
86
-HzhWlXuf9aoUBo7WoNh5UBjvg7omy5rtR0qqxD85Ng4WpR2kTkWStejeN+DErwda
87
-MMZvcaF1V7f4nB1kMQKE2IQ7q9K/E9UJr+/yX9tbLvWP1EzsL12qI/u2zcRXo8R8
88
-iQagIQKBgQDA4Ag/ShDEb0x6rFFJwMaHUT/TT8Yv8Ul7paIcAX6c/1jwgEh9T20F
89
-6UWl+OcIpJp7DYNNLB/hesYqs76QvZDd9nIiW1bzuC0LHuSzyEoe234gpAfrRfYs
90
-qLwYJxjzwYTLvLYPU5vHmdg8v5wIXh0TaRTDTdKViISGD09aiXSYzw==
91
-`)
92
-)
93
-
94 26
 // TestOAuthRequestHeader checks the following scenarios:
95 27
 //  * request containing remote user header is ignored if it doesn't have client cert auth
96
-//  * request containing remote user header is honored if it has client cert auth
28
+//  * request containing remote user header is honored if it has valid client cert auth matching ClientCommonNames
97 29
 //  * unauthenticated requests are redirected to an auth proxy
98 30
 //  * login command succeeds against a request-header identity provider via redirection to an auth proxy
99 31
 func TestOAuthRequestHeader(t *testing.T) {
... ...
@@ -169,10 +98,11 @@ func TestOAuthRequestHeader(t *testing.T) {
169 169
 		UseAsLogin:      true,
170 170
 		MappingMethod:   "claim",
171 171
 		Provider: &configapi.RequestHeaderIdentityProvider{
172
-			ChallengeURL: proxyServer.URL + "/oauth/authorize?${query}",
173
-			LoginURL:     "http://www.example.com/login?then=${url}",
174
-			ClientCA:     caFile.Name(),
175
-			Headers:      []string{"My-Remote-User", "SSO-User"},
172
+			ChallengeURL:      proxyServer.URL + "/oauth/authorize?${query}",
173
+			LoginURL:          "http://www.example.com/login?then=${url}",
174
+			ClientCA:          caFile.Name(),
175
+			ClientCommonNames: []string{"proxy"},
176
+			Headers:           []string{"My-Remote-User", "SSO-User"},
176 177
 		},
177 178
 	}
178 179
 
... ...
@@ -199,77 +129,157 @@ func TestOAuthRequestHeader(t *testing.T) {
199 199
 
200 200
 	// Use the server and CA info, with cert info
201 201
 	proxyConfig := anonConfig
202
-	proxyConfig.CertData = clientCert
203
-	proxyConfig.KeyData = clientKey
202
+	proxyConfig.CertData = proxyClientCert
203
+	proxyConfig.KeyData = proxyClientKey
204 204
 	proxyTransport, err = restclient.TransportFor(&proxyConfig)
205 205
 	if err != nil {
206 206
 		t.Fatalf("unexpected error: %v", err)
207 207
 	}
208 208
 
209
-	// Build the authorize request, spoofing a remote user header
210
-	authorizeURL := clientConfig.Host + "/oauth/authorize?client_id=openshift-challenging-client&response_type=token"
211
-	req, err := http.NewRequest("GET", authorizeURL, nil)
212
-	req.Header.Set("My-Remote-User", "myuser")
213
-
214
-	// Make the request without cert auth
215
-	resp, err := anonTransport.RoundTrip(req)
209
+	// client cert that is valid, but not in the list of allowed common names
210
+	otherCertConfig := anonConfig
211
+	otherCertConfig.CertData = otherClientCert
212
+	otherCertConfig.KeyData = otherClientKey
213
+	otherCertTransport, err := restclient.TransportFor(&otherCertConfig)
216 214
 	if err != nil {
217 215
 		t.Fatalf("unexpected error: %v", err)
218 216
 	}
219
-	proxyRedirect, err := resp.Location()
220
-	if err != nil {
221
-		t.Fatalf("expected spoofed remote user header to get 302 redirect, got error: %v", err)
222
-	}
223
-	if proxyRedirect.String() != proxyServer.URL+"/oauth/authorize?client_id=openshift-challenging-client&response_type=token" {
224
-		t.Fatalf("expected redirect to proxy endpoint, got redirected to %v", proxyRedirect.String())
225
-	}
226
-
227
-	// Request the redirected URL, which should cause the proxy to make the same request with cert auth
228
-	req, err = http.NewRequest("GET", proxyRedirect.String(), nil)
229
-	req.Header.Set("My-Remote-User", "myuser")
230
-	req.SetBasicAuth("myusername", "mypassword")
231 217
 
232
-	resp, err = proxyTransport.RoundTrip(req)
218
+	// client cert that has the desired common name, but does not have a valid signature
219
+	invalidCertConfig := anonConfig
220
+	invalidCertConfig.CertData = invalidClientCert
221
+	invalidCertConfig.KeyData = invalidClientKey
222
+	invalidCertTransport, err := restclient.TransportFor(&invalidCertConfig)
233 223
 	if err != nil {
234 224
 		t.Fatalf("unexpected error: %v", err)
235 225
 	}
236
-	tokenRedirect, err := resp.Location()
237
-	if err != nil {
238
-		t.Fatalf("expected 302 redirect, got error: %v", err)
239
-	}
240
-	if tokenRedirect.Query().Get("error") != "" {
241
-		t.Fatalf("expected successful token request, got error %v", tokenRedirect.String())
242
-	}
243 226
 
244
-	// Extract the access_token
227
+	authorizeURL := clientConfig.Host + "/oauth/authorize?client_id=openshift-challenging-client&response_type=token"
228
+	proxyURL := proxyServer.URL + "/oauth/authorize?client_id=openshift-challenging-client&response_type=token"
245 229
 
246
-	// group #0 is everything.                      #1                #2     #3
247
-	accessTokenRedirectRegex := regexp.MustCompile(`(^|&)access_token=([^&]+)($|&)`)
248
-	accessToken := ""
249
-	if matches := accessTokenRedirectRegex.FindStringSubmatch(tokenRedirect.Fragment); matches != nil {
250
-		accessToken = matches[2]
251
-	}
252
-	if accessToken == "" {
253
-		t.Fatalf("Expected access token, got %s", tokenRedirect.String())
230
+	testcases := map[string]struct {
231
+		transport                http.RoundTripper
232
+		expectDirectRequestError bool
233
+	}{
234
+		"anonymous": {
235
+			transport:                anonTransport,
236
+			expectDirectRequestError: false,
237
+		},
238
+		"valid signature, invalid cn": {
239
+			transport: otherCertTransport,
240
+			// TODO: this should redirect once we add support for client-cert logins
241
+			expectDirectRequestError: true,
242
+		},
243
+		"invalid signature, valid cn": {
244
+			transport: invalidCertTransport,
245
+			// TODO: this should redirect once we add support for client-cert logins
246
+			expectDirectRequestError: true,
247
+		},
254 248
 	}
255 249
 
256
-	// Make sure we can use the token, and it represents who we expect
257
-	userConfig := anonConfig
258
-	userConfig.BearerToken = accessToken
259
-	userClient, err := client.New(&userConfig)
260
-	if err != nil {
261
-		t.Fatalf("Unexpected error: %v", err)
262
-	}
263
-	user, err := userClient.Users().Get("~")
264
-	if err != nil {
265
-		t.Fatalf("Unexpected error: %v", err)
266
-	}
267
-	if user.Name != "myusername" {
268
-		t.Fatalf("Expected myusername as the user, got %v", user)
250
+	for k, tc := range testcases {
251
+		// Build the authorize request, spoofing a remote user header
252
+		directRequest, err := http.NewRequest("GET", authorizeURL, nil)
253
+		directRequest.Header.Set("My-Remote-User", "myuser")
254
+
255
+		// direct request against authorizeURL should redirect to proxy
256
+		directResponse, err := tc.transport.RoundTrip(directRequest)
257
+		if err != nil {
258
+			t.Errorf("%s: unexpected error: %v", k, err)
259
+			continue
260
+		}
261
+
262
+		if tc.expectDirectRequestError {
263
+			if directResponse.StatusCode != 500 {
264
+				body, _ := ioutil.ReadAll(directResponse.Body)
265
+				t.Logf("%s: Status:  %#v", k, directResponse.StatusCode)
266
+				t.Logf("%s: Headers: %#v", k, directResponse.Header)
267
+				t.Logf("%s: Body:    %s", k, string(body))
268
+				t.Errorf("%s: Expected spoofed header to get 500 status code, got %d", k, directResponse.StatusCode)
269
+				continue
270
+			}
271
+		} else {
272
+			proxyRedirect, err := directResponse.Location()
273
+			if err != nil {
274
+				body, _ := ioutil.ReadAll(directResponse.Body)
275
+				t.Logf("%s: Status:  %#v", k, directResponse.StatusCode)
276
+				t.Logf("%s: Headers: %#v", k, directResponse.Header)
277
+				t.Logf("%s: Body:    %s", k, string(body))
278
+				t.Errorf("%s: expected spoofed remote user header to get 302 redirect, got error: %v", k, err)
279
+				continue
280
+			}
281
+			if proxyRedirect.String() != proxyURL {
282
+				t.Errorf("%s: expected redirect to proxy endpoint, got redirected to %v", k, proxyRedirect.String())
283
+				continue
284
+			}
285
+		}
286
+
287
+		// request to proxy without credentials should return 401
288
+		proxyRequest, err := http.NewRequest("GET", proxyURL, nil)
289
+		proxyRequest.Header.Set("My-Remote-User", "myuser")
290
+
291
+		unauthenticatedProxyResponse, err := tc.transport.RoundTrip(proxyRequest)
292
+		if err != nil {
293
+			t.Errorf("%s: unexpected error: %v", k, err)
294
+			continue
295
+		}
296
+		if unauthenticatedProxyResponse.StatusCode != 401 {
297
+			t.Errorf("%s: expected 401 status, got: %v", k, unauthenticatedProxyResponse.StatusCode)
298
+			continue
299
+		}
300
+
301
+		// request to proxy with credentials should succeed with given credentials, not with passed Remote-User header
302
+		proxyRequest.SetBasicAuth("myusername", "mypassword")
303
+
304
+		authenticatedProxyResponse, err := tc.transport.RoundTrip(proxyRequest)
305
+		if err != nil {
306
+			t.Errorf("%s: unexpected error: %v", k, err)
307
+			continue
308
+		}
309
+		tokenRedirect, err := authenticatedProxyResponse.Location()
310
+		if err != nil {
311
+			t.Errorf("%s: expected 302 redirect, got error: %v", k, err)
312
+			continue
313
+		}
314
+		if tokenRedirect.Query().Get("error") != "" {
315
+			t.Errorf("%s: expected successful token request, got error %v", k, tokenRedirect.String())
316
+			continue
317
+		}
318
+
319
+		// Extract the access_token
320
+
321
+		// group #0 is everything.                      #1                #2     #3
322
+		accessTokenRedirectRegex := regexp.MustCompile(`(^|&)access_token=([^&]+)($|&)`)
323
+		accessToken := ""
324
+		if matches := accessTokenRedirectRegex.FindStringSubmatch(tokenRedirect.Fragment); matches != nil {
325
+			accessToken = matches[2]
326
+		}
327
+		if accessToken == "" {
328
+			t.Errorf("%s: Expected access token, got %s", k, tokenRedirect.String())
329
+			continue
330
+		}
331
+
332
+		// Make sure we can use the token, and it represents who we expect
333
+		userConfig := anonConfig
334
+		userConfig.BearerToken = accessToken
335
+		userClient, err := client.New(&userConfig)
336
+		if err != nil {
337
+			t.Errorf("%s: Unexpected error: %v", k, err)
338
+			continue
339
+		}
340
+		user, err := userClient.Users().Get("~")
341
+		if err != nil {
342
+			t.Errorf("%s: Unexpected error: %v", k, err)
343
+			continue
344
+		}
345
+		if user.Name != "myusername" {
346
+			t.Errorf("%s: Expected myusername as the user, got %v", k, user)
347
+			continue
348
+		}
269 349
 	}
270 350
 
271 351
 	// Get the master CA data for the login command
272
-	masterCAFile := userConfig.CAFile
352
+	masterCAFile := anonConfig.CAFile
273 353
 	if masterCAFile == "" {
274 354
 		// Write master ca data
275 355
 		tmpFile, err := ioutil.TempFile("", "ca.crt")
... ...
@@ -277,7 +287,7 @@ func TestOAuthRequestHeader(t *testing.T) {
277 277
 			t.Fatalf("unexpected error: %v", err)
278 278
 		}
279 279
 		defer os.Remove(tmpFile.Name())
280
-		if err := ioutil.WriteFile(tmpFile.Name(), userConfig.CAData, os.FileMode(0600)); err != nil {
280
+		if err := ioutil.WriteFile(tmpFile.Name(), anonConfig.CAData, os.FileMode(0600)); err != nil {
281 281
 			t.Fatalf("unexpected error: %v", err)
282 282
 		}
283 283
 		masterCAFile = tmpFile.Name()
... ...
@@ -302,3 +312,403 @@ func TestOAuthRequestHeader(t *testing.T) {
302 302
 		t.Fatalf("Expected token after authentication: %#v", loginOptions.Config)
303 303
 	}
304 304
 }
305
+
306
+var (
307
+	// oadm ca create-signer-cert --name=test-ca --overwrite=true
308
+	rootCACert = []byte(`
309
+Certificate:
310
+    Data:
311
+        Version: 3 (0x2)
312
+        Serial Number: 1 (0x1)
313
+        Signature Algorithm: sha256WithRSAEncryption
314
+        Issuer: CN=test-ca
315
+        Validity
316
+            Not Before: Apr 11 18:11:23 2016 GMT
317
+            Not After : Mar 18 18:11:24 2116 GMT
318
+        Subject: CN=test-ca
319
+        Subject Public Key Info:
320
+            Public Key Algorithm: rsaEncryption
321
+            RSA Public Key: (2048 bit)
322
+                Modulus (2048 bit):
323
+                    00:b8:e7:dd:d5:05:90:93:bf:21:58:06:bd:00:b2:
324
+                    02:a3:5f:4b:e8:6c:22:26:87:76:22:ff:0a:69:4a:
325
+                    90:c1:b5:2f:b9:09:7d:3e:73:75:04:b9:52:9f:43:
326
+                    44:e8:67:2b:2f:25:06:03:b2:f8:2d:a1:10:8c:de:
327
+                    f7:bf:61:7f:82:bc:4c:aa:c2:af:ea:b3:e3:81:9b:
328
+                    e4:58:c2:99:7e:e3:81:b5:26:57:3b:98:fa:c1:59:
329
+                    90:24:f5:98:6a:e5:c8:1d:6a:31:f0:05:15:b6:c1:
330
+                    17:35:0d:03:eb:c8:bd:19:28:8d:33:b0:40:8b:63:
331
+                    95:3a:80:bb:6c:5f:d7:1e:7b:e4:27:fd:89:6b:52:
332
+                    46:1b:7d:2d:48:b0:3e:42:d3:28:32:ce:2a:7c:d7:
333
+                    66:d1:ec:59:a5:1c:2e:62:78:56:c6:d5:0c:64:5d:
334
+                    2e:51:8e:7c:6e:6c:6b:71:4d:a4:54:55:cb:fc:a5:
335
+                    29:ea:e5:df:36:2f:c6:2b:cf:86:84:54:cf:4e:2b:
336
+                    b1:3f:e2:ea:51:60:72:eb:2c:fc:67:d0:1b:01:21:
337
+                    1c:4a:45:78:fa:d7:7f:87:92:d7:3c:21:4c:8f:0c:
338
+                    90:f0:bc:df:56:1b:c6:2c:9b:cf:fa:38:88:95:53:
339
+                    3a:2d:08:76:d0:2b:67:4c:15:fd:da:ed:83:67:d0:
340
+                    d2:2f
341
+                Exponent: 65537 (0x10001)
342
+        X509v3 extensions:
343
+            X509v3 Key Usage: critical
344
+                Digital Signature, Key Encipherment, Certificate Sign
345
+            X509v3 Basic Constraints: critical
346
+                CA:TRUE
347
+    Signature Algorithm: sha256WithRSAEncryption
348
+        45:44:e3:86:5a:0b:a4:75:57:f4:75:51:cf:19:1c:b8:af:a6:
349
+        4e:80:1f:47:93:26:a4:32:ab:35:f2:e7:67:17:ab:96:8d:9f:
350
+        82:10:d8:f1:e1:9f:3f:93:6d:ba:5d:22:d1:72:4e:d4:d1:f6:
351
+        24:06:00:ee:ac:d4:e4:61:b8:a6:52:04:32:f9:a1:cb:8f:53:
352
+        73:4d:cc:b5:35:32:b9:01:77:bf:db:00:b1:79:62:95:fd:da:
353
+        1e:b6:43:5f:48:05:bb:99:66:49:05:db:14:c3:65:82:77:6d:
354
+        d7:ec:b9:6e:0d:7d:8f:79:72:64:fc:e1:ee:15:0e:45:62:ec:
355
+        ac:3b:b2:dd:bc:84:89:6d:d1:ac:c5:04:79:d4:f6:e0:ee:b3:
356
+        1a:45:db:24:89:38:12:4a:3a:9d:4c:32:7b:cf:ba:a7:5b:44:
357
+        be:3d:44:ca:63:59:3e:19:4e:d2:0c:c8:36:0b:87:22:fd:8e:
358
+        34:ba:60:3c:d7:81:0f:5c:35:7f:6c:64:ae:cd:18:49:a7:07:
359
+        54:cf:7d:94:92:f3:13:a4:f1:6c:2b:aa:4a:5b:30:f9:23:d0:
360
+        1c:e2:56:6d:4d:c5:b9:19:2e:bd:9d:bf:43:2b:e9:8e:ef:e7:
361
+        b6:dd:ea:22:52:ae:e6:94:48:1a:c4:1e:e6:04:b5:c1:86:de:
362
+        49:03:ab:3a
363
+-----BEGIN CERTIFICATE-----
364
+MIICxDCCAaygAwIBAgIBATANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDEwd0ZXN0
365
+LWNhMCAXDTE2MDQxMTE4MTEyM1oYDzIxMTYwMzE4MTgxMTI0WjASMRAwDgYDVQQD
366
+Ewd0ZXN0LWNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuOfd1QWQ
367
+k78hWAa9ALICo19L6GwiJod2Iv8KaUqQwbUvuQl9PnN1BLlSn0NE6GcrLyUGA7L4
368
+LaEQjN73v2F/grxMqsKv6rPjgZvkWMKZfuOBtSZXO5j6wVmQJPWYauXIHWox8AUV
369
+tsEXNQ0D68i9GSiNM7BAi2OVOoC7bF/XHnvkJ/2Ja1JGG30tSLA+QtMoMs4qfNdm
370
+0exZpRwuYnhWxtUMZF0uUY58bmxrcU2kVFXL/KUp6uXfNi/GK8+GhFTPTiuxP+Lq
371
+UWBy6yz8Z9AbASEcSkV4+td/h5LXPCFMjwyQ8LzfVhvGLJvP+jiIlVM6LQh20Ctn
372
+TBX92u2DZ9DSLwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAqQwDwYDVR0TAQH/BAUw
373
+AwEB/zANBgkqhkiG9w0BAQsFAAOCAQEARUTjhloLpHVX9HVRzxkcuK+mToAfR5Mm
374
+pDKrNfLnZxerlo2fghDY8eGfP5Ntul0i0XJO1NH2JAYA7qzU5GG4plIEMvmhy49T
375
+c03MtTUyuQF3v9sAsXlilf3aHrZDX0gFu5lmSQXbFMNlgndt1+y5bg19j3lyZPzh
376
+7hUORWLsrDuy3byEiW3RrMUEedT24O6zGkXbJIk4Eko6nUwye8+6p1tEvj1EymNZ
377
+PhlO0gzINguHIv2ONLpgPNeBD1w1f2xkrs0YSacHVM99lJLzE6TxbCuqSlsw+SPQ
378
+HOJWbU3FuRkuvZ2/Qyvpju/ntt3qIlKu5pRIGsQe5gS1wYbeSQOrOg==
379
+-----END CERTIFICATE-----
380
+`)
381
+
382
+	// oadm create-api-client-config --basename=proxy --client-dir=. --user=proxy
383
+	proxyClientCert = []byte(`
384
+Certificate:
385
+    Data:
386
+        Version: 3 (0x2)
387
+        Serial Number: 2 (0x2)
388
+        Signature Algorithm: sha256WithRSAEncryption
389
+        Issuer: CN=test-ca
390
+        Validity
391
+            Not Before: Apr 11 18:12:17 2016 GMT
392
+            Not After : Mar 18 18:12:18 2116 GMT
393
+        Subject: CN=proxy
394
+        Subject Public Key Info:
395
+            Public Key Algorithm: rsaEncryption
396
+            RSA Public Key: (2048 bit)
397
+                Modulus (2048 bit):
398
+                    00:db:09:79:3d:37:2d:69:2a:2d:57:cf:87:27:a7:
399
+                    6e:07:00:a6:af:71:19:18:1e:f3:04:00:09:54:52:
400
+                    26:67:03:f1:a6:ef:35:e6:73:cb:ee:46:75:11:5f:
401
+                    30:46:dc:d1:fb:2c:68:bb:a8:e0:60:0f:fa:59:f2:
402
+                    d1:40:a1:79:29:83:8e:a6:b6:2c:22:c1:0a:3c:04:
403
+                    74:ae:5d:a1:3d:db:9b:61:ea:bb:d1:77:20:26:fb:
404
+                    c1:ec:e6:9a:0d:ef:df:8c:02:35:27:8b:69:9c:01:
405
+                    a2:f6:bd:f2:a0:43:15:42:05:b2:77:1c:b4:21:58:
406
+                    fd:23:65:e7:bb:e1:3b:9b:ab:0e:fd:5e:15:da:97:
407
+                    3f:23:50:53:67:c9:2c:77:f9:fb:62:ee:4c:df:6b:
408
+                    e1:4e:40:ef:f7:de:ba:6d:fe:32:be:f7:e5:4a:a5:
409
+                    33:5e:ca:84:8c:d4:3e:24:18:9e:a4:b4:a8:02:3d:
410
+                    45:5b:ac:66:06:72:70:ea:14:9b:14:9a:b6:50:29:
411
+                    78:bf:49:80:43:ba:da:8d:03:dc:52:6d:4a:be:2f:
412
+                    5c:1d:2a:27:65:4c:2a:bc:45:69:80:ec:2e:fe:55:
413
+                    81:24:09:b4:2f:b8:5b:77:e3:cc:56:3e:b9:3d:57:
414
+                    91:de:17:08:b2:c6:77:5d:9f:f4:b2:8f:d8:8d:a9:
415
+                    2e:81
416
+                Exponent: 65537 (0x10001)
417
+        X509v3 extensions:
418
+            X509v3 Key Usage: critical
419
+                Digital Signature, Key Encipherment
420
+            X509v3 Extended Key Usage: 
421
+                TLS Web Client Authentication
422
+            X509v3 Basic Constraints: critical
423
+                CA:FALSE
424
+    Signature Algorithm: sha256WithRSAEncryption
425
+        0e:bb:6c:d9:7a:f4:b8:57:a3:ea:d3:36:2b:83:31:3d:ed:48:
426
+        c5:7f:b2:ba:20:33:82:03:22:a4:3e:4c:54:60:66:74:17:be:
427
+        ac:a6:28:86:0f:eb:b0:33:f7:5c:ba:d4:52:97:da:5d:00:04:
428
+        bc:90:61:76:2c:d6:51:37:b9:8a:ea:c3:63:b7:77:01:d1:4a:
429
+        56:98:fb:61:e1:94:b2:fb:c2:da:19:a1:8b:f3:33:fa:4c:b5:
430
+        0f:7f:2b:3b:83:63:48:28:bc:d4:ff:e6:93:ee:a7:3f:b5:47:
431
+        4b:9b:47:96:cb:b5:cc:e7:df:27:24:54:b7:3e:ec:e6:67:52:
432
+        40:78:03:bd:7f:ec:3b:90:56:f1:bb:63:04:f0:6e:43:07:13:
433
+        23:e9:b2:9d:84:25:13:5f:a1:76:3b:d9:72:cf:05:8e:2e:a6:
434
+        9d:9b:68:d4:36:76:95:76:68:4e:1c:90:bb:22:c4:6d:3c:bd:
435
+        16:bf:57:06:de:f6:76:1a:2a:10:dc:f5:d9:8f:23:a6:39:49:
436
+        34:66:6d:74:2c:81:2d:0f:49:a4:d2:f3:8c:a9:dc:72:8b:7b:
437
+        2b:95:37:9a:f5:b4:7f:9d:61:fe:04:c1:53:48:bc:26:8e:f8:
438
+        01:8f:ac:24:4d:44:ac:7d:4d:fd:5b:a2:ff:b9:33:33:2e:83:
439
+        81:d2:66:54
440
+-----BEGIN CERTIFICATE-----
441
+MIIC1DCCAbygAwIBAgIBAjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDEwd0ZXN0
442
+LWNhMCAXDTE2MDQxMTE4MTIxN1oYDzIxMTYwMzE4MTgxMjE4WjAQMQ4wDAYDVQQD
443
+EwVwcm94eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANsJeT03LWkq
444
+LVfPhyenbgcApq9xGRge8wQACVRSJmcD8abvNeZzy+5GdRFfMEbc0fssaLuo4GAP
445
++lny0UCheSmDjqa2LCLBCjwEdK5doT3bm2Hqu9F3ICb7wezmmg3v34wCNSeLaZwB
446
+ova98qBDFUIFsncctCFY/SNl57vhO5urDv1eFdqXPyNQU2fJLHf5+2LuTN9r4U5A
447
+7/feum3+Mr735UqlM17KhIzUPiQYnqS0qAI9RVusZgZycOoUmxSatlApeL9JgEO6
448
+2o0D3FJtSr4vXB0qJ2VMKrxFaYDsLv5VgSQJtC+4W3fjzFY+uT1Xkd4XCLLGd12f
449
+9LKP2I2pLoECAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsG
450
+AQUFBwMCMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggEBAA67bNl69LhX
451
+o+rTNiuDMT3tSMV/srogM4IDIqQ+TFRgZnQXvqymKIYP67Az91y61FKX2l0ABLyQ
452
+YXYs1lE3uYrqw2O3dwHRSlaY+2HhlLL7wtoZoYvzM/pMtQ9/KzuDY0govNT/5pPu
453
+pz+1R0ubR5bLtczn3yckVLc+7OZnUkB4A71/7DuQVvG7YwTwbkMHEyPpsp2EJRNf
454
+oXY72XLPBY4upp2baNQ2dpV2aE4ckLsixG08vRa/Vwbe9nYaKhDc9dmPI6Y5STRm
455
+bXQsgS0PSaTS84yp3HKLeyuVN5r1tH+dYf4EwVNIvCaO+AGPrCRNRKx9Tf1bov+5
456
+MzMug4HSZlQ=
457
+-----END CERTIFICATE-----`)
458
+
459
+	proxyClientKey = []byte(`
460
+-----BEGIN RSA PRIVATE KEY-----
461
+MIIEowIBAAKCAQEA2wl5PTctaSotV8+HJ6duBwCmr3EZGB7zBAAJVFImZwPxpu81
462
+5nPL7kZ1EV8wRtzR+yxou6jgYA/6WfLRQKF5KYOOprYsIsEKPAR0rl2hPdubYeq7
463
+0XcgJvvB7OaaDe/fjAI1J4tpnAGi9r3yoEMVQgWydxy0IVj9I2Xnu+E7m6sO/V4V
464
+2pc/I1BTZ8ksd/n7Yu5M32vhTkDv9966bf4yvvflSqUzXsqEjNQ+JBiepLSoAj1F
465
+W6xmBnJw6hSbFJq2UCl4v0mAQ7rajQPcUm1Kvi9cHSonZUwqvEVpgOwu/lWBJAm0
466
+L7hbd+PMVj65PVeR3hcIssZ3XZ/0so/YjakugQIDAQABAoIBAEenNrkW1s0jVgf2
467
+xLDtLaouxVh5OAtS/I6fcG3cHeHvQVspv8kuslS1SdCwAfv8etie83gIS7ZBI9XP
468
+ADMTX6578euJhrCr06xEjOMJkBjLQW5ruptQS/1UuGDGIzlR8iA8DKVuDtNRGb17
469
+7+YLa+XYNUSP6EFMeirdSEyG5tgKJ32j1SQAtIedhnrMRtdIfjLmus0c2efIXvgz
470
+f26d3OclRy50X+P61jns/5ya+aocYKfzQ3Gp8ZKeIGZ6vw3tgID18eQ8vrjUJJWk
471
+43UQg+axShqJTm/+unLpS3dJcXSSMu1OCzdCOnyiYiqL0KhJy8YC7doTQTQTq7VJ
472
+SBkoQtECgYEA8S1I4FtSwU+Wv0Wa8b7QticOGOxDzfnEgkLQHC1I0hVnATKGKpvN
473
+luOT8UBblwZssozFJ0UzpKWbPWYS7l+4G634A7qxu7+Byv9BwZITyg36LlGEbJXU
474
+god22G6+Z6HeSQIFPElp8nY+UtpuzXmdvijlm3/RzViDiJx2PcS5A4UCgYEA6H/W
475
+IMge3Alc0imTK3TIrNP6sPjztvKv6JVxrxN5GAzHx3mlwj8m+0naXeXqo/6krq52
476
+wGaUCZPzehpN6R6H/d5VfANa9x1wCBHCPBEhPN+rnFiUlT2q5uNcg9uDwYisxJ96
477
++aInjsPaoCxAIHJFmxxFJH26Y7JBQydRoGRR+c0CgYEA8ILEhlkMMhN4tc5oMmSk
478
+JsLT4C7df29xdKXEfBT85eTKD/ueqKcvYyYYxyHzNK0HgRe5FOyCD9PG+HfusSFr
479
+rM7U4oMv85eLjDD6FlviuEEwGTjZ4p+YiYMmFbh60UYvMod9SR29NjqM9Hs4vFhn
480
+4tdOAsB5LVrz8Sx3Dio8hzECgYBWe8bw5r/j5W+rlV9zGLvU3f0we0pc0SVyBLUH
481
+BN1UftyJbMyl1svvSWd66h0/52bmu2rc4stKTMiSsNouTvcTDfMKcE0UAtU7iy+P
482
+HGgatrClNaX/ZbL+s7AkNDFseiSZ9yDNXu4MAvp9/jfUWe1eZ0Oo8UO19gakrimE
483
+2gxMOQKBgGYuj4I5TmtROJDTRxrRRzQVnoTV3GesequaW/y5UM4xr2ThLevnPCMI
484
+dSmVatvGsqYsQbPKOAp3ZcMQiqTIPUFeYSuzs3TuTs/tZ1cwfseN4M2bs258ynsT
485
+51PHf9W7BDmvHZn8JMg688SSklatAUj4h3nnGWco1de9YL1bBLPg
486
+-----END RSA PRIVATE KEY-----
487
+`)
488
+
489
+	// oadm create-api-client-config --basename=other --client-dir=. --user=other
490
+	otherClientCert = []byte(`
491
+Certificate:
492
+    Data:
493
+        Version: 3 (0x2)
494
+        Serial Number: 3 (0x3)
495
+        Signature Algorithm: sha256WithRSAEncryption
496
+        Issuer: CN=test-ca
497
+        Validity
498
+            Not Before: Apr 11 18:12:25 2016 GMT
499
+            Not After : Mar 18 18:12:26 2116 GMT
500
+        Subject: CN=other
501
+        Subject Public Key Info:
502
+            Public Key Algorithm: rsaEncryption
503
+            RSA Public Key: (2048 bit)
504
+                Modulus (2048 bit):
505
+                    00:d8:6d:9e:41:51:d3:e9:99:b9:6d:37:4d:72:32:
506
+                    6d:e8:3e:01:38:15:30:cd:5c:fb:0e:e1:76:01:32:
507
+                    38:cf:1b:0e:8c:ec:21:5b:87:25:aa:6b:ac:6d:4b:
508
+                    a9:a5:c4:5e:aa:43:32:70:96:9f:30:dd:c7:ba:0f:
509
+                    a8:de:73:72:b5:10:9f:55:0a:80:bf:10:cc:c7:e3:
510
+                    55:9b:6d:e1:13:6b:c2:d7:be:1c:c4:29:7b:db:06:
511
+                    bd:7e:22:a9:be:1a:af:cb:59:98:cf:0d:a5:e7:f7:
512
+                    cc:cd:92:05:3e:c8:a6:1e:cf:a3:05:90:b8:a8:76:
513
+                    7a:a4:44:78:82:e4:7d:ba:1b:6e:4b:6f:1b:39:96:
514
+                    04:c3:ec:28:1f:ac:c5:36:09:2e:71:23:00:35:44:
515
+                    6e:ac:73:7b:5a:ad:c9:5c:35:4e:0c:5f:d6:09:9c:
516
+                    a0:a5:2c:ce:d7:5e:d6:93:e1:9c:b4:ec:61:bb:9f:
517
+                    ff:32:dc:64:9a:d5:bf:7f:20:84:a9:e7:5d:69:b6:
518
+                    87:42:e6:a2:31:1c:32:50:6a:20:18:3e:f6:f8:c7:
519
+                    b8:63:eb:a2:35:da:4f:eb:34:f3:e5:e8:da:06:fd:
520
+                    c9:19:4e:45:b3:5d:e8:be:ed:18:e8:b5:30:42:eb:
521
+                    70:64:72:76:03:30:04:81:38:f3:7c:09:98:5b:1d:
522
+                    0f:dd
523
+                Exponent: 65537 (0x10001)
524
+        X509v3 extensions:
525
+            X509v3 Key Usage: critical
526
+                Digital Signature, Key Encipherment
527
+            X509v3 Extended Key Usage: 
528
+                TLS Web Client Authentication
529
+            X509v3 Basic Constraints: critical
530
+                CA:FALSE
531
+    Signature Algorithm: sha256WithRSAEncryption
532
+        61:bf:f3:81:d2:c9:46:e3:bb:68:0d:ae:b3:ce:56:1f:bf:3b:
533
+        93:ba:65:54:04:37:25:5e:bf:2a:b6:79:2f:bd:17:3f:eb:85:
534
+        9a:ce:78:ff:f8:b5:5a:3d:f9:99:1d:24:41:2c:0d:d1:c9:63:
535
+        19:19:75:b2:a6:65:da:d6:a5:ae:31:57:ec:8f:d6:0d:d9:86:
536
+        5e:b8:f1:98:a7:43:12:1c:d0:71:d2:5c:2f:a3:bb:5f:89:fc:
537
+        dd:9a:fc:fb:8a:9b:ed:73:3b:6d:25:90:c9:70:96:88:d0:67:
538
+        d7:10:17:35:e9:6e:d4:2b:61:f6:d0:4d:02:75:73:7a:cf:03:
539
+        ed:d2:e2:3b:6f:cf:58:2e:92:e8:b6:c2:e1:1b:5d:33:46:3f:
540
+        95:53:67:7a:69:92:be:2d:e8:59:cd:71:16:a4:a4:89:80:ee:
541
+        67:97:47:84:a8:0e:f7:fe:7c:2e:97:b1:f5:11:84:30:90:1d:
542
+        a7:44:55:15:93:c9:fc:16:16:28:2c:cd:8c:1d:82:a0:ff:35:
543
+        61:ec:8e:ae:59:88:bf:87:55:85:79:cd:20:58:79:c3:6b:4d:
544
+        78:43:c0:48:44:6d:78:24:e2:26:24:99:97:81:b9:43:a4:6d:
545
+        1e:dd:31:53:5b:36:49:cc:df:58:e8:f2:a8:25:30:cd:69:a8:
546
+        c1:0d:c7:84
547
+-----BEGIN CERTIFICATE-----
548
+MIIC1DCCAbygAwIBAgIBAzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDEwd0ZXN0
549
+LWNhMCAXDTE2MDQxMTE4MTIyNVoYDzIxMTYwMzE4MTgxMjI2WjAQMQ4wDAYDVQQD
550
+EwVvdGhlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANhtnkFR0+mZ
551
+uW03TXIybeg+ATgVMM1c+w7hdgEyOM8bDozsIVuHJaprrG1LqaXEXqpDMnCWnzDd
552
+x7oPqN5zcrUQn1UKgL8QzMfjVZtt4RNrwte+HMQpe9sGvX4iqb4ar8tZmM8Npef3
553
+zM2SBT7Iph7PowWQuKh2eqREeILkfbobbktvGzmWBMPsKB+sxTYJLnEjADVEbqxz
554
+e1qtyVw1Tgxf1gmcoKUsztde1pPhnLTsYbuf/zLcZJrVv38ghKnnXWm2h0LmojEc
555
+MlBqIBg+9vjHuGProjXaT+s08+Xo2gb9yRlORbNd6L7tGOi1MELrcGRydgMwBIE4
556
+83wJmFsdD90CAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsG
557
+AQUFBwMCMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggEBAGG/84HSyUbj
558
+u2gNrrPOVh+/O5O6ZVQENyVevyq2eS+9Fz/rhZrOeP/4tVo9+ZkdJEEsDdHJYxkZ
559
+dbKmZdrWpa4xV+yP1g3Zhl648ZinQxIc0HHSXC+ju1+J/N2a/PuKm+1zO20lkMlw
560
+lojQZ9cQFzXpbtQrYfbQTQJ1c3rPA+3S4jtvz1gukui2wuEbXTNGP5VTZ3ppkr4t
561
+6FnNcRakpImA7meXR4SoDvf+fC6XsfURhDCQHadEVRWTyfwWFigszYwdgqD/NWHs
562
+jq5ZiL+HVYV5zSBYecNrTXhDwEhEbXgk4iYkmZeBuUOkbR7dMVNbNknM31jo8qgl
563
+MM1pqMENx4Q=
564
+-----END CERTIFICATE-----`)
565
+
566
+	otherClientKey = []byte(`
567
+-----BEGIN RSA PRIVATE KEY-----
568
+MIIEpAIBAAKCAQEA2G2eQVHT6Zm5bTdNcjJt6D4BOBUwzVz7DuF2ATI4zxsOjOwh
569
+W4clqmusbUuppcReqkMycJafMN3Hug+o3nNytRCfVQqAvxDMx+NVm23hE2vC174c
570
+xCl72wa9fiKpvhqvy1mYzw2l5/fMzZIFPsimHs+jBZC4qHZ6pER4guR9uhtuS28b
571
+OZYEw+woH6zFNgkucSMANURurHN7Wq3JXDVODF/WCZygpSzO117Wk+GctOxhu5//
572
+MtxkmtW/fyCEqeddabaHQuaiMRwyUGogGD72+Me4Y+uiNdpP6zTz5ejaBv3JGU5F
573
+s13ovu0Y6LUwQutwZHJ2AzAEgTjzfAmYWx0P3QIDAQABAoIBAQCQF8Nid8lf4NIc
574
+jdJJMpwMIKQNI8afI8We7ar0NuytrrTsTBYVaxA/u3pMNjDXxbrFHwIJBa8tCKt+
575
+DAkBOdnoBQ4fv2NiUhwVBR0s42YT2Q4bN17Nl1T3yTAGN6vNftUFzTw4tjx8CXZY
576
+c1x8pXg8UT+XZ/gZaPBUR6X4d4nhikGqSiILNh6uDjuYUxxMgea0qrAnx4HvBcBF
577
+I1X5zg+turWcuTXoR39Ijn3UNnNZrp8XUqjA850dzQQnZqrcDnD0lqV1HOcF5V2H
578
+VABBIVL8Jzm7mn+k6+NTVC3eWFK+EPwY8/OwHGa3O9LsA0l3knsG8x4FvaRFXTSY
579
+fYSkExudAoGBANwE79pvjGE9kJ5MgJVg7klNv5XWIoaZnmiaPovM5LX7Fylw6vV3
580
+QGEj8x3VO3EqBB66g70SrqoNgCmVNx3oe8+KVX1+8XVhX4DIQLbLJBFfvaNZV7sh
581
+IOM00hhwdKZk+szb2CS3yRo2rsmD1yEr7djsnC1l7UiLn4TJ9bA6oK+nAoGBAPvS
582
+V07KwKKFQUv7cLwPDy1b8G7JbFOmYuc8Zber3S9YtrpFKjX2lR9bXk6iPSa2k485
583
+cqs1RM2/Mrw0uXKpW3jrwVE6dy2IyKLuMBcvKWlUVY02cGA0hV7A2CJKGcPxsFEP
584
+txj5R+VN/FDcm7RzE0jmJNay+5PmDfchom4WXFTbAoGAWhHXUvfpYwF+C5/L39sf
585
+kXi3npJb7fhDZhUG19pYIruYvslQFo7sFxhNdYAOZoRJzX6TYbqdMFZ4ig1g0+iR
586
+juPVnZtzI5dqLmFMRMiiik5EZvOzO5MTUJAWFhUrW9bo6SZytI1cUVPjd/F2B0lh
587
+hDVQtjEM0279LbIz1yIZF+8CgYEArRMlRJcfjNPPTBy1n9st0DwXZN11YYzC/zDI
588
+rFMoAymS9TUiTNJ8LYALsjnZk6j6g/607C0Ba/OUODx4lPEHWHWYeW6YiKgxVaIl
589
+VVnpuWXoItUeqVCPtc8O/Yo2aTDMwPnvGvAB1P0jhKQLNBu/TmQ3P4TmWgFM6eSp
590
+Eca2kO8CgYAstEpSdnMQAHE92HsTSBA+aFm5jfYE/2papDcVE/Q2AqMN+ZjZvfnj
591
+vWyX2MBY8yccNwUyNiwbEfiy9A+XLtNpsuVvNGzOp5CQAs/wIPTCfRD7zVUtIhUN
592
+PVEo4cjWU2JK68lSTyW3UWdoPwcKIdDlnure/al7NpIG2g6weBubpQ==
593
+-----END RSA PRIVATE KEY-----
594
+`)
595
+
596
+	// invalidClientCert is a client cert with the desired name which is NOT signed by the root CA crt, but by another signer with the same name
597
+	// oadm ca create-signer-cert --name=test-ca --overwrite=true
598
+	// oadm create-api-client-config --basename=invalid --client-dir=. --user=invalid
599
+	invalidClientCert = []byte(`
600
+Certificate:
601
+    Data:
602
+        Version: 3 (0x2)
603
+        Serial Number: 2 (0x2)
604
+        Signature Algorithm: sha256WithRSAEncryption
605
+        Issuer: CN=test-ca
606
+        Validity
607
+            Not Before: Apr 11 18:17:29 2016 GMT
608
+            Not After : Mar 18 18:17:30 2116 GMT
609
+        Subject: CN=invalid
610
+        Subject Public Key Info:
611
+            Public Key Algorithm: rsaEncryption
612
+            RSA Public Key: (2048 bit)
613
+                Modulus (2048 bit):
614
+                    00:ac:87:5e:71:36:39:6d:2b:33:40:0e:ff:d6:3d:
615
+                    67:a5:8b:3d:7e:56:c5:3f:49:a9:42:7d:6f:da:30:
616
+                    cc:0f:cd:64:cc:20:91:e4:41:b2:9c:54:f8:9a:fe:
617
+                    ba:7d:e6:2b:2f:ff:fc:c8:7b:4f:bf:3d:61:5c:18:
618
+                    6e:10:6f:a8:33:9e:54:8f:f7:ac:34:57:f4:ff:00:
619
+                    c7:24:07:dd:df:47:e1:bc:0f:d6:41:b5:e4:5c:c0:
620
+                    36:90:4b:2e:b2:97:a9:2c:7f:c4:f7:7a:2b:96:1b:
621
+                    a4:20:ba:db:df:4b:72:ff:e2:ae:46:79:5b:5d:72:
622
+                    41:16:3d:a4:c5:31:cf:12:0c:ca:59:d5:72:c9:fe:
623
+                    87:51:b4:54:0f:eb:46:79:95:8b:2b:ba:a3:51:71:
624
+                    87:06:c2:5b:80:59:74:c4:d8:bd:c6:7f:56:e9:8f:
625
+                    95:d1:85:1f:67:39:20:33:1f:3a:ba:9a:81:c6:32:
626
+                    b6:a6:e3:1d:15:97:19:c9:71:9e:95:ec:d3:38:3b:
627
+                    2a:28:37:f8:cf:ea:c3:3c:af:84:b9:d6:64:8f:e1:
628
+                    cd:29:d3:9a:ba:48:82:50:85:0f:07:d2:d4:e9:83:
629
+                    42:9f:22:25:4d:55:9d:38:32:9c:f1:07:17:14:bf:
630
+                    80:7b:c5:88:6e:f7:60:50:ab:95:32:a3:0f:98:74:
631
+                    49:21
632
+                Exponent: 65537 (0x10001)
633
+        X509v3 extensions:
634
+            X509v3 Key Usage: critical
635
+                Digital Signature, Key Encipherment
636
+            X509v3 Extended Key Usage: 
637
+                TLS Web Client Authentication
638
+            X509v3 Basic Constraints: critical
639
+                CA:FALSE
640
+    Signature Algorithm: sha256WithRSAEncryption
641
+        92:5d:46:49:82:df:80:84:8c:7a:4d:4a:4c:ab:13:20:70:ee:
642
+        36:42:84:3c:30:67:51:a6:b4:c8:e2:0f:13:c4:2b:51:9f:2d:
643
+        7a:0d:be:77:3e:90:67:81:55:f8:3d:b5:c6:00:a1:ca:86:d1:
644
+        83:67:d6:7d:4c:e9:c0:af:53:3b:23:5d:17:1f:8b:c0:c5:ae:
645
+        a3:f2:7c:b5:7b:9a:fc:1b:09:a8:78:ed:12:13:fd:ed:97:0c:
646
+        e4:eb:f8:b2:63:d1:bf:89:db:84:1e:45:f8:5b:5b:d2:93:c2:
647
+        26:5c:61:b4:a9:05:30:45:5e:f5:c4:95:8e:98:83:4d:41:61:
648
+        5d:cb:83:a6:72:b6:af:70:64:8a:72:5a:1f:20:cb:8b:7c:82:
649
+        52:26:45:9d:58:da:c8:0b:e0:ac:00:f0:d4:12:85:2c:2b:a5:
650
+        29:db:54:e6:83:e3:48:d2:61:65:e6:13:31:09:cd:c8:ba:39:
651
+        3c:f7:ca:ab:93:ea:21:12:5f:49:0d:46:17:15:cc:ae:72:a8:
652
+        66:97:56:f3:2f:39:75:b5:f9:3e:ff:5a:4f:3b:8c:16:4d:bf:
653
+        70:55:c5:b7:ee:74:d7:39:4b:da:f9:da:39:84:25:62:24:a8:
654
+        b8:f3:2d:6b:e5:71:60:26:cb:71:ad:bc:25:2a:f9:3a:ec:25:
655
+        b9:c3:5c:e4
656
+-----BEGIN CERTIFICATE-----
657
+MIIC1jCCAb6gAwIBAgIBAjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDEwd0ZXN0
658
+LWNhMCAXDTE2MDQxMTE4MTcyOVoYDzIxMTYwMzE4MTgxNzMwWjASMRAwDgYDVQQD
659
+EwdpbnZhbGlkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArIdecTY5
660
+bSszQA7/1j1npYs9flbFP0mpQn1v2jDMD81kzCCR5EGynFT4mv66feYrL//8yHtP
661
+vz1hXBhuEG+oM55Uj/esNFf0/wDHJAfd30fhvA/WQbXkXMA2kEsuspepLH/E93or
662
+lhukILrb30ty/+KuRnlbXXJBFj2kxTHPEgzKWdVyyf6HUbRUD+tGeZWLK7qjUXGH
663
+BsJbgFl0xNi9xn9W6Y+V0YUfZzkgMx86upqBxjK2puMdFZcZyXGelezTODsqKDf4
664
+z+rDPK+EudZkj+HNKdOaukiCUIUPB9LU6YNCnyIlTVWdODKc8QcXFL+Ae8WIbvdg
665
+UKuVMqMPmHRJIQIDAQABozUwMzAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYI
666
+KwYBBQUHAwIwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAkl1GSYLf
667
+gISMek1KTKsTIHDuNkKEPDBnUaa0yOIPE8QrUZ8teg2+dz6QZ4FV+D21xgChyobR
668
+g2fWfUzpwK9TOyNdFx+LwMWuo/J8tXua/BsJqHjtEhP97ZcM5Ov4smPRv4nbhB5F
669
++Ftb0pPCJlxhtKkFMEVe9cSVjpiDTUFhXcuDpnK2r3BkinJaHyDLi3yCUiZFnVja
670
+yAvgrADw1BKFLCulKdtU5oPjSNJhZeYTMQnNyLo5PPfKq5PqIRJfSQ1GFxXMrnKo
671
+ZpdW8y85dbX5Pv9aTzuMFk2/cFXFt+501zlL2vnaOYQlYiSouPMta+VxYCbLca28
672
+JSr5OuwlucNc5A==
673
+-----END CERTIFICATE-----`)
674
+
675
+	invalidClientKey = []byte(`
676
+-----BEGIN RSA PRIVATE KEY-----
677
+MIIEowIBAAKCAQEArIdecTY5bSszQA7/1j1npYs9flbFP0mpQn1v2jDMD81kzCCR
678
+5EGynFT4mv66feYrL//8yHtPvz1hXBhuEG+oM55Uj/esNFf0/wDHJAfd30fhvA/W
679
+QbXkXMA2kEsuspepLH/E93orlhukILrb30ty/+KuRnlbXXJBFj2kxTHPEgzKWdVy
680
+yf6HUbRUD+tGeZWLK7qjUXGHBsJbgFl0xNi9xn9W6Y+V0YUfZzkgMx86upqBxjK2
681
+puMdFZcZyXGelezTODsqKDf4z+rDPK+EudZkj+HNKdOaukiCUIUPB9LU6YNCnyIl
682
+TVWdODKc8QcXFL+Ae8WIbvdgUKuVMqMPmHRJIQIDAQABAoIBAE7weTPPnaLnm0F6
683
+G3DJE71Y4kAGL6XvbDRx9FWe8h9g2PfVByurK6//6OfyGR41zBjgRabtVOWpjfx3
684
+aRS4IfvMO+DLb81bWUu77WH8/3WEDDLiBCR4tw4BHHYVED7CybMEmviou3ypFQWs
685
+uaGHggy2iQrRyA4Pktw8REG9soMM+s+T0zlfexbeXgz7OJYd5QStBslI5ZJhHa1I
686
+LW94hrU0Yj1ONP2hCfMc5H808zOkMUSZMgRMTXXZQzo2XdujLelRxQ1oaZTU4aQM
687
+SwZG1vzdDjjFhXW1sjD7G2DoyTxbDVOIfqYOmO/t7witXo2g52hIXhb4RFoxD0eZ
688
+dFDRyakCgYEA0mU9X2rc1GU4OlKUOd4phsQfLmRk4ya7i2Zica3n4PFvaz+XoIZ0
689
+NZf2xwZlY5MX21tBadIFW8C4GilDscgLrP8P2gW15hP6bKpHuB2nkJcGdNi6Tg8f
690
+0Vvf7L4RHamolN7yk39zJoC9KUDaPAtqGB4niUDCsjc6LdJTqW/oCHMCgYEA0ezs
691
+Dxe8j9l34BEqZxu71NjUKovUQJv+0kz301W5gVCFAY5oBDfFf42IHl5q+B7TFLaP
692
+6xEEdyh0K6GrGTt9YtWB3HYPEStzBf0fXAg7fJHPn12mn8/B0UFMkJKyuRnPOW7t
693
+3WOROzChSpWWKVUMFJIBCyfMvEjUgm8SzUluxxsCgYBe8g8DK09ijhcUwsVfY/Fr
694
+fr/viKC6nXUPEHImiOtWaL32MSl06Jgyw1Q7Npi0meGvPPxFC+EdKdgq/iotZXBX
695
+bncx1Vfj72oYdbON09wVdQIV4uQYa9zY9tQTmyZQM4r/O6lOhLprSreSkVCqvh/v
696
+qFQBLXdvQ1r+6KaWlQiqHwKBgG7s6FeZTVQdr5BAwc02BGyWHpZUyNVTGLV7YkDT
697
+vXAtYfrOivwflEawPMr/TTrK3vLE/QtTNK7aO3iKtuRgYQMGmtYptBB4ixERDa8N
698
+0pEiYzlsvQ0ZNOsjvBdwzOuuTaeljB8965IBQlks7entPLLp648/epnLSi+aDa9Y
699
+LCcdAoGBAL1qU+P+8+8Lfdj+/8GfaZdZQnqeOzLkxzGDcuVDNYACIVGmGJqL01Gx
700
+RUVzUCnG68qdoai960Yo0w41U90hsCpBny50SShYu3kL67Gqn03UcEYDw5pmUPwn
701
+NykkJ6u51LArDs6E8hA0aoMrQbZGZiod93dPHlbFhR3+4t4l8wDJ
702
+-----END RSA PRIVATE KEY-----
703
+`)
704
+)