func TestX509Verifier(t *testing.T) { testCases := map[string]struct { Insecure bool Certs []*x509.Certificate Opts x509.VerifyOptions ExpectOK bool ExpectErr bool }{ "non-tls": { Insecure: true, ExpectOK: false, ExpectErr: false, }, "tls, no certs": { ExpectOK: false, ExpectErr: false, }, "self signed": { Opts: getDefaultVerifyOptions(t), Certs: getCerts(t, selfSignedCert), ExpectErr: true, }, "server cert disallowed": { Opts: getDefaultVerifyOptions(t), Certs: getCerts(t, serverCert), ExpectErr: true, }, "server cert allowing non-client cert usages": { Opts: x509.VerifyOptions{Roots: getRootCertPool(t)}, Certs: getCerts(t, serverCert), ExpectOK: true, ExpectErr: false, }, "valid client cert": { Opts: getDefaultVerifyOptions(t), Certs: getCerts(t, clientCNCert), ExpectOK: true, ExpectErr: false, }, "future cert": { Opts: x509.VerifyOptions{ CurrentTime: time.Now().Add(time.Duration(-100 * time.Hour * 24 * 365)), Roots: getRootCertPool(t), }, Certs: getCerts(t, clientCNCert), ExpectOK: false, ExpectErr: true, }, "expired cert": { Opts: x509.VerifyOptions{ CurrentTime: time.Now().Add(time.Duration(100 * time.Hour * 24 * 365)), Roots: getRootCertPool(t), }, Certs: getCerts(t, clientCNCert), ExpectOK: false, ExpectErr: true, }, } for k, testCase := range testCases { req, _ := http.NewRequest("GET", "/", nil) if !testCase.Insecure { req.TLS = &tls.ConnectionState{PeerCertificates: testCase.Certs} } authCall := false auth := authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) { authCall = true return &user.DefaultInfo{Name: "innerauth"}, true, nil }) a := NewVerifier(testCase.Opts, auth) user, ok, err := a.AuthenticateRequest(req) if testCase.ExpectErr && err == nil { t.Errorf("%s: Expected error, got none", k) continue } if !testCase.ExpectErr && err != nil { t.Errorf("%s: Got unexpected error: %v", k, err) continue } if testCase.ExpectOK != ok { t.Errorf("%s: Expected ok=%v, got %v", k, testCase.ExpectOK, ok) continue } if testCase.ExpectOK { if !authCall { t.Errorf("%s: Expected inner auth called, wasn't", k) continue } if "innerauth" != user.GetName() { t.Errorf("%s: Expected user.name=%v, got %v", k, "innerauth", user.GetName()) continue } } else { if authCall { t.Errorf("%s: Expected inner auth not to be called, was", k) continue } } } }
func TestX509(t *testing.T) { testCases := map[string]struct { Insecure bool Certs []*x509.Certificate Opts x509.VerifyOptions User UserConversion ExpectUserName string ExpectOK bool ExpectErr bool }{ "non-tls": { Insecure: true, ExpectOK: false, ExpectErr: false, }, "tls, no certs": { ExpectOK: false, ExpectErr: false, }, "self signed": { Opts: getDefaultVerifyOptions(t), Certs: getCerts(t, selfSignedCert), User: CommonNameUserConversion, ExpectErr: true, }, "server cert": { Opts: getDefaultVerifyOptions(t), Certs: getCerts(t, serverCert), User: CommonNameUserConversion, ExpectErr: true, }, "server cert allowing non-client cert usages": { Opts: x509.VerifyOptions{Roots: getRootCertPool(t)}, Certs: getCerts(t, serverCert), User: CommonNameUserConversion, ExpectUserName: "******", ExpectOK: true, ExpectErr: false, }, "common name": { Opts: getDefaultVerifyOptions(t), Certs: getCerts(t, clientCNCert), User: CommonNameUserConversion, ExpectUserName: "******", ExpectOK: true, ExpectErr: false, }, "empty dns": { Opts: getDefaultVerifyOptions(t), Certs: getCerts(t, clientCNCert), User: DNSNameUserConversion, ExpectOK: false, ExpectErr: false, }, "dns": { Opts: getDefaultVerifyOptions(t), Certs: getCerts(t, clientDNSCert), User: DNSNameUserConversion, ExpectUserName: "******", ExpectOK: true, ExpectErr: false, }, "empty email": { Opts: getDefaultVerifyOptions(t), Certs: getCerts(t, clientCNCert), User: EmailAddressUserConversion, ExpectOK: false, ExpectErr: false, }, "email": { Opts: getDefaultVerifyOptions(t), Certs: getCerts(t, clientEmailCert), User: EmailAddressUserConversion, ExpectUserName: "******", ExpectOK: true, ExpectErr: false, }, "custom conversion error": { Opts: getDefaultVerifyOptions(t), Certs: getCerts(t, clientCNCert), User: UserConversionFunc(func(chain []*x509.Certificate) (user.Info, bool, error) { return nil, false, errors.New("custom error") }), ExpectOK: false, ExpectErr: true, }, "custom conversion success": { Opts: getDefaultVerifyOptions(t), Certs: getCerts(t, clientCNCert), User: UserConversionFunc(func(chain []*x509.Certificate) (user.Info, bool, error) { return &user.DefaultInfo{Name: "custom"}, true, nil }), ExpectUserName: "******", ExpectOK: true, ExpectErr: false, }, "future cert": { Opts: x509.VerifyOptions{ CurrentTime: time.Now().Add(time.Duration(-100 * time.Hour * 24 * 365)), Roots: getRootCertPool(t), }, Certs: getCerts(t, clientCNCert), User: CommonNameUserConversion, ExpectOK: false, ExpectErr: true, }, "expired cert": { Opts: x509.VerifyOptions{ CurrentTime: time.Now().Add(time.Duration(100 * time.Hour * 24 * 365)), Roots: getRootCertPool(t), }, Certs: getCerts(t, clientCNCert), User: CommonNameUserConversion, ExpectOK: false, ExpectErr: true, }, } for k, testCase := range testCases { req, _ := http.NewRequest("GET", "/", nil) if !testCase.Insecure { req.TLS = &tls.ConnectionState{PeerCertificates: testCase.Certs} } a := New(testCase.Opts, testCase.User) user, ok, err := a.AuthenticateRequest(req) if testCase.ExpectErr && err == nil { t.Errorf("%s: Expected error, got none", k) continue } if !testCase.ExpectErr && err != nil { t.Errorf("%s: Got unexpected error: %v", k, err) continue } if testCase.ExpectOK != ok { t.Errorf("%s: Expected ok=%v, got %v", k, testCase.ExpectOK, ok) continue } if testCase.ExpectOK { if testCase.ExpectUserName != user.GetName() { t.Errorf("%s: Expected user.name=%v, got %v", k, testCase.ExpectUserName, user.GetName()) continue } } } }
return nil, false, kerrors.NewAggregate(errlist) } // DefaultVerifyOptions returns VerifyOptions that use the system root certificates, current time, // and requires certificates to be valid for client auth (x509.ExtKeyUsageClientAuth) func DefaultVerifyOptions() x509.VerifyOptions { return x509.VerifyOptions{ KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, } } // SubjectToUserConversion calls SubjectToUser on the subject of the first certificate in the chain. // If the resulting user has no name, it returns nil, false, nil var SubjectToUserConversion = UserConversionFunc(func(chain []*x509.Certificate) (user.Info, bool, error) { user := SubjectToUser(chain[0].Subject) if len(user.GetName()) == 0 { return nil, false, nil } return user, true, nil }) // CommonNameUserConversion builds user info from a certificate chain using the subject's CommonName var CommonNameUserConversion = UserConversionFunc(func(chain []*x509.Certificate) (user.Info, bool, error) { if len(chain[0].Subject.CommonName) == 0 { return nil, false, nil } return &user.DefaultInfo{Name: chain[0].Subject.CommonName}, true, nil }) // DNSNameUserConversion builds user info from a certificate chain using the first DNSName on the certificate var DNSNameUserConversion = UserConversionFunc(func(chain []*x509.Certificate) (user.Info, bool, error) {
func TestRequestHeader(t *testing.T) { testcases := map[string]struct { ConfiguredHeaders []string RequestHeaders http.Header ExpectedUsername string }{ "empty": { ExpectedUsername: "", }, "no match": { ConfiguredHeaders: []string{"X-Remote-User"}, ExpectedUsername: "", }, "match": { ConfiguredHeaders: []string{"X-Remote-User"}, RequestHeaders: http.Header{"X-Remote-User": {"Bob"}}, ExpectedUsername: "******", }, "exact match": { ConfiguredHeaders: []string{"X-Remote-User"}, RequestHeaders: http.Header{ "Prefixed-X-Remote-User-With-Suffix": {"Bob"}, "X-Remote-User-With-Suffix": {"Bob"}, }, ExpectedUsername: "", }, "first match": { ConfiguredHeaders: []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"}}, ExpectedUsername: "******", }, "case-insensitive": { ConfiguredHeaders: []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 ExpectedUsername: "******", }, } for k, testcase := range testcases { mapper := &TestUserIdentityMapper{} auth := NewAuthenticator("testprovider", &Config{testcase.ConfiguredHeaders}, mapper) req := &http.Request{Header: testcase.RequestHeaders} user, ok, err := auth.AuthenticateRequest(req) if testcase.ExpectedUsername == "" { if ok { t.Errorf("%s: Didn't expect user, authentication succeeded", k) continue } } if testcase.ExpectedUsername != "" { if err != nil { t.Errorf("%s: Expected user, got error: %v", k, err) continue } if !ok { t.Errorf("%s: Expected user, auth failed", k) continue } if testcase.ExpectedUsername != user.GetName() { t.Errorf("%s: Expected username %s, got %s", k, testcase.ExpectedUsername, user.GetName()) continue } } } }
func TestBasicAuth(t *testing.T) { testCases := map[string]struct { Header string Password testPassword ExpectedCalled bool ExpectedUsername string ExpectedPassword string ExpectedUser string ExpectedOK bool ExpectedErr bool }{ "no header": { Header: "", }, "non-basic header": { Header: "Bearer foo", }, "empty value basic header": { Header: "Basic", }, "whitespace value basic header": { Header: "Basic ", }, "non base-64 basic header": { Header: "Basic !@#$", ExpectedErr: true, }, "malformed basic header": { Header: "Basic " + base64.StdEncoding.EncodeToString([]byte("user_without_password")), ExpectedErr: true, }, "empty password basic header": { Header: "Basic " + base64.StdEncoding.EncodeToString([]byte("user_with_empty_password:"******"user_with_empty_password", ExpectedPassword: "", }, "valid basic header": { Header: "Basic " + base64.StdEncoding.EncodeToString([]byte("myuser:mypassword:withcolon")), ExpectedCalled: true, ExpectedUsername: "******", ExpectedPassword: "******", }, "password auth returned user": { Header: "Basic " + base64.StdEncoding.EncodeToString([]byte("myuser:mypw")), Password: testPassword{User: &user.DefaultInfo{Name: "returneduser"}, OK: true}, ExpectedCalled: true, ExpectedUsername: "******", ExpectedPassword: "******", ExpectedUser: "******", ExpectedOK: true, }, "password auth returned error": { Header: "Basic " + base64.StdEncoding.EncodeToString([]byte("myuser:mypw")), Password: testPassword{Err: errors.New("auth error")}, ExpectedCalled: true, ExpectedUsername: "******", ExpectedPassword: "******", ExpectedErr: true, }, } for k, testCase := range testCases { password := testCase.Password auth := authenticator.Request(New(&password)) req, _ := http.NewRequest("GET", "/", nil) if testCase.Header != "" { req.Header.Set("Authorization", testCase.Header) } user, ok, err := auth.AuthenticateRequest(req) if testCase.ExpectedCalled != password.Called { t.Fatalf("%s: Expected called=%v, got %v", k, testCase.ExpectedCalled, password.Called) continue } if testCase.ExpectedUsername != password.Username { t.Fatalf("%s: Expected called with username=%v, got %v", k, testCase.ExpectedUsername, password.Username) continue } if testCase.ExpectedPassword != password.Password { t.Fatalf("%s: Expected called with password=%v, got %v", k, testCase.ExpectedPassword, password.Password) continue } if testCase.ExpectedErr != (err != nil) { t.Fatalf("%s: Expected err=%v, got err=%v", k, testCase.ExpectedErr, err) continue } if testCase.ExpectedOK != ok { t.Fatalf("%s: Expected ok=%v, got ok=%v", k, testCase.ExpectedOK, ok) continue } if testCase.ExpectedUser != "" && testCase.ExpectedUser != user.GetName() { t.Fatalf("%s: Expected user.GetName()=%v, got %v", k, testCase.ExpectedUser, user.GetName()) continue } } }