func (impersonateAuthorizer) Authorize(ctx kapi.Context, a authorizer.AuthorizationAttributes) (allowed bool, reason string, err error) { user, exists := kapi.UserFrom(ctx) if !exists { return false, "missing user", nil } switch { case user.GetName() == "system:admin": return true, "", nil case user.GetName() == "tester": return false, "", fmt.Errorf("works on my machine") case user.GetName() == "deny-me": return false, "denied", nil } if len(user.GetGroups()) == 1 && user.GetGroups()[0] == "wheel" && a.GetVerb() == "impersonate" && a.GetResource() == "systemusers" { return true, "", nil } if len(user.GetGroups()) == 1 && user.GetGroups()[0] == "sa-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "serviceaccounts" { return true, "", nil } if len(user.GetGroups()) == 1 && user.GetGroups()[0] == "regular-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "users" { return true, "", nil } return false, "deny by default", nil }
func (impersonateAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) { user := a.GetUser() switch { case user.GetName() == "system:admin": return true, "", nil case user.GetName() == "tester": return false, "", fmt.Errorf("works on my machine") case user.GetName() == "deny-me": return false, "denied", nil } if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "wheel" && a.GetVerb() == "impersonate" && a.GetResource() == "users" { return true, "", nil } if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "sa-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "serviceaccounts" { return true, "", nil } if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "regular-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "users" { return true, "", nil } if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "group-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "groups" { return true, "", nil } if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-scopes" && a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "scopes" { return true, "", nil } if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-particular-scopes" && a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "scopes" && a.GetName() == "scope-a" { return true, "", nil } if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-project" && a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "project" { return true, "", nil } return false, "deny by default", nil }
func (s *Storage) Update(ctx api.Context, name string, obj rest.UpdatedObjectInfo) (runtime.Object, bool, error) { if user, ok := api.UserFrom(ctx); ok { if s.superUser != "" && user.GetName() == s.superUser { return s.StandardStorage.Update(ctx, name, obj) } } nonEscalatingInfo := wrapUpdatedObjectInfo(obj, func(ctx api.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) { clusterRole := obj.(*rbac.ClusterRole) rules := clusterRole.Rules if err := validation.ConfirmNoEscalation(ctx, s.ruleResolver, rules); err != nil { return nil, errors.NewForbidden(groupResource, clusterRole.Name, err) } return obj, nil }) return s.StandardStorage.Update(ctx, name, nonEscalatingInfo) }
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 } } } }
func TestX509Verifier(t *testing.T) { multilevelOpts := DefaultVerifyOptions() multilevelOpts.Roots = x509.NewCertPool() multilevelOpts.Roots.AddCert(getCertsFromFile(t, "root")[0]) testCases := map[string]struct { Insecure bool Certs []*x509.Certificate Opts x509.VerifyOptions AllowedCNs sets.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), 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, }, "valid client cert with wrong CN": { Opts: getDefaultVerifyOptions(t), AllowedCNs: sets.NewString("foo", "bar"), Certs: getCerts(t, clientCNCert), ExpectOK: false, ExpectErr: true, }, "valid client cert with right CN": { Opts: getDefaultVerifyOptions(t), AllowedCNs: sets.NewString("client_cn"), Certs: getCerts(t, clientCNCert), ExpectOK: true, ExpectErr: false, }, "future cert": { Opts: x509.VerifyOptions{ CurrentTime: time.Now().Add(-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(100 * time.Hour * 24 * 365), Roots: getRootCertPool(t), }, Certs: getCerts(t, clientCNCert), ExpectOK: false, ExpectErr: true, }, "multi-level, valid": { Opts: multilevelOpts, Certs: getCertsFromFile(t, "client-valid", "intermediate"), ExpectOK: true, ExpectErr: false, }, "multi-level, expired": { Opts: multilevelOpts, Certs: getCertsFromFile(t, "client-expired", "intermediate"), 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, testCase.AllowedCNs) 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 } } } }
// startServiceAccountTestServer returns a started server // It is the responsibility of the caller to ensure the returned stopFunc is called func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclient.Config, func()) { // Listener h := &framework.MasterHolder{Initialized: make(chan struct{})} apiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { <-h.Initialized h.M.GenericAPIServer.Handler.ServeHTTP(w, req) })) // Anonymous client config clientConfig := restclient.Config{Host: apiServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: ®istered.GroupOrDie(v1.GroupName).GroupVersion}} // Root client // TODO: remove rootClient after we refactor pkg/admission to use the clientset. rootClientset := clientset.NewForConfigOrDie(&restclient.Config{Host: apiServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: ®istered.GroupOrDie(v1.GroupName).GroupVersion}, BearerToken: rootToken}) internalRootClientset := internalclientset.NewForConfigOrDie(&restclient.Config{Host: apiServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: ®istered.GroupOrDie(v1.GroupName).GroupVersion}, BearerToken: rootToken}) // Set up two authenticators: // 1. A token authenticator that maps the rootToken to the "root" user // 2. A ServiceAccountToken authenticator that validates ServiceAccount tokens rootTokenAuth := authenticator.TokenFunc(func(token string) (user.Info, bool, error) { if token == rootToken { return &user.DefaultInfo{Name: rootUserName}, true, nil } return nil, false, nil }) serviceAccountKey, _ := rsa.GenerateKey(rand.Reader, 2048) serviceAccountTokenGetter := serviceaccountcontroller.NewGetterFromClient(rootClientset) serviceAccountTokenAuth := serviceaccount.JWTTokenAuthenticator([]interface{}{&serviceAccountKey.PublicKey}, true, serviceAccountTokenGetter) authenticator := union.New( bearertoken.New(rootTokenAuth), bearertoken.New(serviceAccountTokenAuth), ) // Set up a stub authorizer: // 1. The "root" user is allowed to do anything // 2. ServiceAccounts named "ro" are allowed read-only operations in their namespace // 3. ServiceAccounts named "rw" are allowed any operation in their namespace authorizer := authorizer.AuthorizerFunc(func(attrs authorizer.Attributes) (bool, string, error) { username := "" if user := attrs.GetUser(); user != nil { username = user.GetName() } ns := attrs.GetNamespace() // If the user is "root"... if username == rootUserName { // allow them to do anything return true, "", nil } // If the user is a service account... if serviceAccountNamespace, serviceAccountName, err := serviceaccount.SplitUsername(username); err == nil { // Limit them to their own namespace if serviceAccountNamespace == ns { switch serviceAccountName { case readOnlyServiceAccountName: if attrs.IsReadOnly() { return true, "", nil } case readWriteServiceAccountName: return true, "", nil } } } return false, fmt.Sprintf("User %s is denied (ns=%s, readonly=%v, resource=%s)", username, ns, attrs.IsReadOnly(), attrs.GetResource()), nil }) // Set up admission plugin to auto-assign serviceaccounts to pods serviceAccountAdmission := serviceaccountadmission.NewServiceAccount(internalRootClientset) masterConfig := framework.NewMasterConfig() masterConfig.GenericConfig.EnableIndex = true masterConfig.GenericConfig.Authenticator = authenticator masterConfig.GenericConfig.Authorizer = authorizer masterConfig.GenericConfig.AdmissionControl = serviceAccountAdmission framework.RunAMasterUsingServer(masterConfig, apiServer, h) // Start the service account and service account token controllers stopCh := make(chan struct{}) tokenController := serviceaccountcontroller.NewTokensController(rootClientset, serviceaccountcontroller.TokensControllerOptions{TokenGenerator: serviceaccount.JWTTokenGenerator(serviceAccountKey)}) go tokenController.Run(1, stopCh) informers := informers.NewSharedInformerFactory(rootClientset, nil, controller.NoResyncPeriodFunc()) serviceAccountController := serviceaccountcontroller.NewServiceAccountsController(informers.ServiceAccounts(), informers.Namespaces(), rootClientset, serviceaccountcontroller.DefaultServiceAccountsControllerOptions()) informers.Start(stopCh) go serviceAccountController.Run(5, stopCh) // Start the admission plugin reflectors serviceAccountAdmission.Run() stop := func() { close(stopCh) serviceAccountAdmission.Stop() apiServer.Close() } return rootClientset, clientConfig, stop }
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 TestRoundTrip(t *testing.T) { // Start with origin attributes oattrs := oauthorizer.DefaultAuthorizationAttributes{ Verb: "get", APIVersion: "av", APIGroup: "ag", Resource: "r", ResourceName: "rn", RequestAttributes: "ra", NonResourceURL: true, URL: "/123", } // Convert to kube attributes kattrs := KubernetesAuthorizerAttributes("ns", &user.DefaultInfo{Name: "myuser", Groups: []string{"mygroup"}}, oattrs) if kattrs.GetUser().GetName() != "myuser" { t.Errorf("Expected %v, got %v", "myuser", kattrs.GetUser().GetName()) } if !reflect.DeepEqual(kattrs.GetUser().GetGroups(), []string{"mygroup"}) { t.Errorf("Expected %v, got %v", []string{"mygroup"}, kattrs.GetUser().GetGroups()) } if kattrs.GetVerb() != "get" { t.Errorf("Expected %v, got %v", "get", kattrs.GetVerb()) } if kattrs.IsReadOnly() != true { t.Errorf("Expected %v, got %v", true, kattrs.IsReadOnly()) } if kattrs.GetNamespace() != "ns" { t.Errorf("Expected %v, got %v", "ns", kattrs.GetNamespace()) } if kattrs.GetResource() != "r" { t.Errorf("Expected %v, got %v", "", kattrs.GetResource()) } if kattrs.IsResourceRequest() != false { t.Errorf("Expected %v, got %v", false, kattrs.IsResourceRequest()) } if kattrs.GetPath() != "/123" { t.Errorf("Expected %v, got %v", "/123", kattrs.GetPath()) } // Convert back to context+origin attributes ctx, oattrs2 := OriginAuthorizerAttributes(kattrs) // Ensure namespace/user info is preserved if user, ok := kapi.UserFrom(ctx); !ok { t.Errorf("No user in context") } else if user.GetName() != "myuser" { t.Errorf("Expected %v, got %v", "myuser", user.GetName()) } else if !reflect.DeepEqual(user.GetGroups(), []string{"mygroup"}) { t.Errorf("Expected %v, got %v", []string{"mygroup"}, user.GetGroups()) } // Ensure common attribute info is preserved if oattrs.GetVerb() != oattrs2.GetVerb() { t.Errorf("Expected %v, got %v", oattrs.GetVerb(), oattrs2.GetVerb()) } if oattrs.GetResource() != oattrs2.GetResource() { t.Errorf("Expected %v, got %v", oattrs.GetResource(), oattrs2.GetResource()) } // Ensure origin-specific info is preserved if oattrs.GetAPIVersion() != oattrs2.GetAPIVersion() { t.Errorf("Expected %v, got %v", oattrs.GetAPIVersion(), oattrs2.GetAPIVersion()) } if oattrs.GetAPIGroup() != oattrs2.GetAPIGroup() { t.Errorf("Expected %v, got %v", oattrs.GetAPIGroup(), oattrs2.GetAPIGroup()) } if oattrs.GetResourceName() != oattrs2.GetResourceName() { t.Errorf("Expected %v, got %v", oattrs.GetResourceName(), oattrs2.GetResourceName()) } if oattrs.GetRequestAttributes() != oattrs2.GetRequestAttributes() { t.Errorf("Expected %v, got %v", oattrs.GetRequestAttributes(), oattrs2.GetRequestAttributes()) } if oattrs.IsNonResourceURL() != oattrs2.IsNonResourceURL() { t.Errorf("Expected %v, got %v", oattrs.IsNonResourceURL(), oattrs2.IsNonResourceURL()) } if oattrs.GetURL() != oattrs2.GetURL() { t.Errorf("Expected %v, got %v", oattrs.GetURL(), oattrs2.GetURL()) } }
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 } } }
func TestX509(t *testing.T) { multilevelOpts := DefaultVerifyOptions() multilevelOpts.Roots = x509.NewCertPool() multilevelOpts.Roots.AddCert(getCertsFromFile(t, "root")[0]) testCases := map[string]struct { Insecure bool Certs []*x509.Certificate Opts x509.VerifyOptions User UserConversion ExpectUserName string ExpectGroups []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: "******", ExpectGroups: []string{"My Org"}, ExpectOK: true, ExpectErr: false, }, "common name": { Opts: getDefaultVerifyOptions(t), Certs: getCerts(t, clientCNCert), User: CommonNameUserConversion, ExpectUserName: "******", ExpectGroups: []string{"My Org"}, ExpectOK: true, ExpectErr: false, }, "ca with multiple organizations": { Opts: x509.VerifyOptions{ Roots: getRootCertPoolFor(t, caWithGroups), }, Certs: getCerts(t, caWithGroups), User: CommonNameUserConversion, ExpectUserName: "******", ExpectGroups: []string{"My Org", "My Org 1", "My Org 2"}, 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, }, "multi-level, valid": { Opts: multilevelOpts, Certs: getCertsFromFile(t, "client-valid", "intermediate"), User: CommonNameUserConversion, ExpectUserName: "******", ExpectOK: true, ExpectErr: false, }, "multi-level, expired": { Opts: multilevelOpts, Certs: getCertsFromFile(t, "client-expired", "intermediate"), 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()) } groups := user.GetGroups() sort.Strings(testCase.ExpectGroups) sort.Strings(groups) if !reflect.DeepEqual(testCase.ExpectGroups, groups) { t.Errorf("%s: Expected user.groups=%v, got %v", k, testCase.ExpectGroups, groups) } } } }
func TestAnyAuthPassword(t *testing.T) { a := New("foo", &testUserIdentityMapper{}) testcases := map[string]struct { Username string Password string ExpectedUsername string ExpectedOK bool ExpectedErr bool }{ "empty user invalid": { Username: "", Password: "******", ExpectedOK: false, }, "empty password invalid": { Username: "******", Password: "", ExpectedOK: false, }, "valid username and password": { Username: "******", Password: "******", ExpectedOK: true, ExpectedUsername: "******", }, "case-sensitive username": { Username: "******", Password: "******", ExpectedOK: true, ExpectedUsername: "******", }, "whitespace-normalizing username": { Username: "******", Password: "******", ExpectedOK: true, ExpectedUsername: "******", }, "whitespace-only user invalid": { Username: "******", Password: "******", ExpectedOK: false, }, } for k, tc := range testcases { user, ok, err := a.AuthenticatePassword(tc.Username, tc.Password) if tc.ExpectedErr != (err != nil) { t.Errorf("%s: expected error=%v, got %v", k, tc.ExpectedErr, err) continue } if tc.ExpectedOK != ok { t.Errorf("%s: expected ok=%v, got %v", k, tc.ExpectedOK, ok) continue } username := "" if ok { username = user.GetName() } if tc.ExpectedUsername != username { t.Errorf("%s: expected username=%v, got %v", k, tc.ExpectedUsername, username) 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 auth": {}, "empty password basic header": { ExpectedCalled: true, ExpectedUsername: "******", ExpectedPassword: "", }, "valid basic header": { ExpectedCalled: true, ExpectedUsername: "******", ExpectedPassword: "******", }, "password auth returned user": { Password: testPassword{User: &user.DefaultInfo{Name: "returneduser"}, OK: true}, ExpectedCalled: true, ExpectedUsername: "******", ExpectedPassword: "******", ExpectedUser: "******", ExpectedOK: true, }, "password auth returned error": { 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.ExpectedUsername != "" || testCase.ExpectedPassword != "" { req.SetBasicAuth(testCase.ExpectedUsername, testCase.ExpectedPassword) } user, ok, err := auth.AuthenticateRequest(req) if testCase.ExpectedCalled != password.Called { t.Errorf("%s: Expected called=%v, got %v", k, testCase.ExpectedCalled, password.Called) continue } if testCase.ExpectedUsername != password.Username { t.Errorf("%s: Expected called with username=%v, got %v", k, testCase.ExpectedUsername, password.Username) continue } if testCase.ExpectedPassword != password.Password { t.Errorf("%s: Expected called with password=%v, got %v", k, testCase.ExpectedPassword, password.Password) continue } if testCase.ExpectedErr != (err != nil) { t.Errorf("%s: Expected err=%v, got err=%v", k, testCase.ExpectedErr, err) continue } if testCase.ExpectedOK != ok { t.Errorf("%s: Expected ok=%v, got ok=%v", k, testCase.ExpectedOK, ok) continue } if testCase.ExpectedUser != "" && testCase.ExpectedUser != user.GetName() { t.Errorf("%s: Expected user.GetName()=%v, got %v", k, testCase.ExpectedUser, user.GetName()) continue } } }
func TestTLSConfig(t *testing.T) { // Verify the cert/key pair works. cert1 := path.Join(os.TempDir(), "oidc-cert-1") key1 := path.Join(os.TempDir(), "oidc-key-1") cert2 := path.Join(os.TempDir(), "oidc-cert-2") key2 := path.Join(os.TempDir(), "oidc-key-2") defer os.Remove(cert1) defer os.Remove(key1) defer os.Remove(cert2) defer os.Remove(key2) oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert1, key1) oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert2, key2) tests := []struct { testCase string serverCertFile string serverKeyFile string trustedCertFile string wantErr bool }{ { testCase: "provider using untrusted custom cert", serverCertFile: cert1, serverKeyFile: key1, wantErr: true, }, { testCase: "provider using untrusted cert", serverCertFile: cert1, serverKeyFile: key1, trustedCertFile: cert2, wantErr: true, }, { testCase: "provider using trusted cert", serverCertFile: cert1, serverKeyFile: key1, trustedCertFile: cert1, wantErr: false, }, } for _, tc := range tests { func() { op := oidctesting.NewOIDCProvider(t, "") srv, err := op.ServeTLSWithKeyPair(tc.serverCertFile, tc.serverKeyFile) if err != nil { t.Errorf("%s: %v", tc.testCase, err) return } defer srv.Close() issuer := srv.URL clientID := "client-foo" options := OIDCOptions{ IssuerURL: srv.URL, ClientID: clientID, CAFile: tc.trustedCertFile, UsernameClaim: "email", GroupsClaim: "groups", } authenticator, err := New(options) if err != nil { t.Errorf("%s: failed to initialize authenticator: %v", tc.testCase, err) return } defer authenticator.Close() email := "*****@*****.**" groups := []string{"group1", "group2"} sort.Strings(groups) token := generateGoodToken(t, op, issuer, "user-1", clientID, "email", email, "groups", groups) // Because this authenticator behaves differently for subsequent requests, run these // tests multiple times (but expect the same result). for i := 1; i < 4; i++ { user, ok, err := authenticator.AuthenticateToken(token) if err != nil { if !tc.wantErr { t.Errorf("%s (req #%d): failed to authenticate token: %v", tc.testCase, i, err) } continue } if tc.wantErr { t.Errorf("%s (req #%d): expected error authenticating", tc.testCase, i) continue } if !ok { t.Errorf("%s (req #%d): did not get user or error", tc.testCase, i) continue } if gotUsername := user.GetName(); email != gotUsername { t.Errorf("%s (req #%d): GetName() expected=%q got %q", tc.testCase, i, email, gotUsername) } gotGroups := user.GetGroups() sort.Strings(gotGroups) if !reflect.DeepEqual(gotGroups, groups) { t.Errorf("%s (req #%d): GetGroups() expected=%q got %q", tc.testCase, i, groups, gotGroups) } } }() } }
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 TestRequestHeader(t *testing.T) { testcases := map[string]struct { Config Config RequestHeaders http.Header ExpectedUsername string ExpectedIdentity api.UserIdentityInfo }{ "empty": { ExpectedUsername: "", }, "no match": { Config: Config{IDHeaders: []string{"X-Remote-User"}}, ExpectedUsername: "", }, "match": { Config: Config{IDHeaders: []string{"X-Remote-User"}}, RequestHeaders: http.Header{"X-Remote-User": {"Bob"}}, ExpectedUsername: "******", }, "exact match": { Config: Config{IDHeaders: []string{"X-Remote-User"}}, RequestHeaders: http.Header{ "Prefixed-X-Remote-User-With-Suffix": {"Bob"}, "X-Remote-User-With-Suffix": {"Bob"}, }, ExpectedUsername: "", }, "first match": { Config: Config{IDHeaders: []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": { Config: Config{IDHeaders: []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: "******", }, "extended attributes": { Config: Config{ IDHeaders: []string{"x-id", "x-id2"}, PreferredUsernameHeaders: []string{"x-preferred-username", "x-preferred-username2"}, EmailHeaders: []string{"x-email", "x-email2"}, NameHeaders: []string{"x-name", "x-name2"}, }, RequestHeaders: http.Header{ "X-Id2": {"12345"}, "X-Preferred-Username2": {"bob"}, "X-Email2": {"*****@*****.**"}, "X-Name2": {"Bob"}, }, ExpectedUsername: "******", ExpectedIdentity: &api.DefaultUserIdentityInfo{ ProviderName: "testprovider", ProviderUserName: "******", Extra: map[string]string{ api.IdentityDisplayNameKey: "Bob", api.IdentityEmailKey: "*****@*****.**", api.IdentityPreferredUsernameKey: "bob", }, }, }, } for k, testcase := range testcases { mapper := &TestUserIdentityMapper{} auth := NewAuthenticator("testprovider", &testcase.Config, 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 } } if testcase.ExpectedIdentity != nil { if !reflect.DeepEqual(testcase.ExpectedIdentity.GetExtra(), mapper.Identity.GetExtra()) { t.Errorf("%s: Expected %#v, got %#v", k, testcase.ExpectedIdentity.GetExtra(), mapper.Identity.GetExtra()) } if !reflect.DeepEqual(testcase.ExpectedIdentity.GetProviderUserName(), mapper.Identity.GetProviderUserName()) { t.Errorf("%s: Expected %#v, got %#v", k, testcase.ExpectedIdentity.GetProviderUserName(), mapper.Identity.GetProviderUserName()) } } } }
func TestKeystoneAuth(t *testing.T) { testCases := map[string]struct { Header string keystoneAuthenticator testKeystoneAuthenticator 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("user1:")), ExpectedOK: false, }, "valid basic header": { Header: "Basic " + base64.StdEncoding.EncodeToString([]byte("user1:password1:withcolon")), ExpectedOK: false, ExpectedErr: false, }, "password auth returned user": { Header: "Basic " + base64.StdEncoding.EncodeToString([]byte("user1:password1")), ExpectedCalled: true, ExpectedUsername: "******", ExpectedPassword: "******", ExpectedOK: true, }, "password auth returned error": { Header: "Basic " + base64.StdEncoding.EncodeToString([]byte("user1:password2")), ExpectedCalled: true, ExpectedUsername: "******", ExpectedPassword: "******", ExpectedErr: false, ExpectedOK: false, }, } for k, testCase := range testCases { ksAuth := testCase.keystoneAuthenticator auth := basicauth.New(&ksAuth) req, _ := http.NewRequest("GET", "/", nil) if testCase.Header != "" { req.Header.Set("Authorization", testCase.Header) } user, ok, err := auth.AuthenticateRequest(req) if testCase.ExpectedErr && err == nil { t.Errorf("%s: Expected error, got none", k) continue } if !testCase.ExpectedErr && err != nil { t.Errorf("%s: Did not expect error, got err:%v", k, err) continue } if testCase.ExpectedOK != ok { t.Errorf("%s: Expected ok=%v, got %v", k, testCase.ExpectedOK, ok) continue } if testCase.ExpectedOK { if testCase.ExpectedUsername != user.GetName() { t.Errorf("%s: Expected user.name=%v, got %v", k, testCase.ExpectedUsername, user.GetName()) continue } } } }