// newServiceAccountAuthenticator returns an authenticator.Request or an error func newServiceAccountAuthenticator(keyfile string, lookup bool, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Request, error) { publicKey, err := serviceaccount.ReadPublicKey(keyfile) if err != nil { return nil, err } tokenAuthenticator := serviceaccount.JWTTokenAuthenticator([]*rsa.PublicKey{publicKey}, lookup, serviceAccountGetter) return bearertoken.New(tokenAuthenticator), nil }
func newAuthenticator(config configapi.MasterConfig, restOptionsGetter restoptions.Getter, tokenGetter serviceaccount.ServiceAccountTokenGetter, apiClientCAs *x509.CertPool, groupMapper identitymapper.UserToGroupMapper) (authenticator.Request, error) { authenticators := []authenticator.Request{} // ServiceAccount token if len(config.ServiceAccountConfig.PublicKeyFiles) > 0 { publicKeys := []*rsa.PublicKey{} for _, keyFile := range config.ServiceAccountConfig.PublicKeyFiles { publicKey, err := serviceaccount.ReadPublicKey(keyFile) if err != nil { return nil, fmt.Errorf("Error reading service account key file %s: %v", keyFile, err) } publicKeys = append(publicKeys, publicKey) } tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(publicKeys, true, tokenGetter) authenticators = append(authenticators, bearertoken.New(tokenAuthenticator, true)) } // OAuth token if config.OAuthConfig != nil { tokenAuthenticator, err := getEtcdTokenAuthenticator(restOptionsGetter, groupMapper) if err != nil { return nil, fmt.Errorf("Error building OAuth token authenticator: %v", err) } tokenRequestAuthenticators := []authenticator.Request{ bearertoken.New(tokenAuthenticator, true), // Allow token as access_token param for WebSockets paramtoken.New("access_token", tokenAuthenticator, true), } authenticators = append(authenticators, // if you have a bearer token, you're a human (usually) // if you change this, have a look at the impersonationFilter where we attach groups to the impersonated user group.NewGroupAdder(unionrequest.NewUnionAuthentication(tokenRequestAuthenticators...), []string{bootstrappolicy.AuthenticatedOAuthGroup})) } if configapi.UseTLS(config.ServingInfo.ServingInfo) { // build cert authenticator // TODO: add "system:" prefix in authenticator, limit cert to username // TODO: add "system:" prefix to groups in authenticator, limit cert to group name opts := x509request.DefaultVerifyOptions() opts.Roots = apiClientCAs certauth := x509request.New(opts, x509request.SubjectToUserConversion) authenticators = append(authenticators, certauth) } ret := &unionrequest.Authenticator{ FailOnError: true, Handlers: []authenticator.Request{ // if you change this, have a look at the impersonationFilter where we attach groups to the impersonated user group.NewGroupAdder(unionrequest.NewUnionAuthentication(authenticators...), []string{bootstrappolicy.AuthenticatedGroup}), anonymous.NewAuthenticator(), }, } return ret, nil }
// newServiceAccountAuthenticator returns an authenticator.Request or an error func newServiceAccountAuthenticator(keyfiles []string, lookup bool, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Request, error) { allPublicKeys := []interface{}{} for _, keyfile := range keyfiles { publicKeys, err := serviceaccount.ReadPublicKeys(keyfile) if err != nil { return nil, err } allPublicKeys = append(allPublicKeys, publicKeys...) } tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(allPublicKeys, lookup, serviceAccountGetter) return bearertoken.New(tokenAuthenticator), nil }
func newAuthenticator(config configapi.MasterConfig, etcdHelper storage.Interface, tokenGetter serviceaccount.ServiceAccountTokenGetter, apiClientCAs *x509.CertPool, groupMapper identitymapper.UserToGroupMapper) authenticator.Request { authenticators := []authenticator.Request{} // ServiceAccount token if len(config.ServiceAccountConfig.PublicKeyFiles) > 0 { publicKeys := []*rsa.PublicKey{} for _, keyFile := range config.ServiceAccountConfig.PublicKeyFiles { publicKey, err := serviceaccount.ReadPublicKey(keyFile) if err != nil { glog.Fatalf("Error reading service account key file %s: %v", keyFile, err) } publicKeys = append(publicKeys, publicKey) } tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(publicKeys, true, tokenGetter) authenticators = append(authenticators, bearertoken.New(tokenAuthenticator, true)) } // OAuth token if config.OAuthConfig != nil { tokenAuthenticator := getEtcdTokenAuthenticator(etcdHelper, groupMapper) authenticators = append(authenticators, bearertoken.New(tokenAuthenticator, true)) // Allow token as access_token param for WebSockets authenticators = append(authenticators, paramtoken.New("access_token", tokenAuthenticator, true)) } if configapi.UseTLS(config.ServingInfo.ServingInfo) { // build cert authenticator // TODO: add "system:" prefix in authenticator, limit cert to username // TODO: add "system:" prefix to groups in authenticator, limit cert to group name opts := x509request.DefaultVerifyOptions() opts.Roots = apiClientCAs certauth := x509request.New(opts, x509request.SubjectToUserConversion) authenticators = append(authenticators, certauth) } // TODO: make anonymous auth optional? ret := &unionrequest.Authenticator{ FailOnError: true, Handlers: []authenticator.Request{ group.NewGroupAdder(unionrequest.NewUnionAuthentication(authenticators...), []string{bootstrappolicy.AuthenticatedGroup}), authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) { return &user.DefaultInfo{Name: unauthenticatedUsername, Groups: []string{bootstrappolicy.UnauthenticatedGroup}}, true, nil }), }, } return ret }
// 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()) { deleteAllEtcdKeys() // Listener var m *master.Master apiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { m.Handler.ServeHTTP(w, req) })) // Anonymous client config clientConfig := restclient.Config{Host: apiServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.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: testapi.Default.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{rootUserName, "", []string{}}, true, nil } return nil, false, nil }) serviceAccountKey, _ := rsa.GenerateKey(rand.Reader, 2048) serviceAccountTokenGetter := serviceaccountcontroller.NewGetterFromClient(rootClientset) serviceAccountTokenAuth := serviceaccount.JWTTokenAuthenticator([]*rsa.PublicKey{&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) error { username := attrs.GetUserName() ns := attrs.GetNamespace() // If the user is "root"... if username == rootUserName { // allow them to do anything return 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 nil } case readWriteServiceAccountName: return nil } } } return fmt.Errorf("User %s is denied (ns=%s, readonly=%v, resource=%s)", username, ns, attrs.IsReadOnly(), attrs.GetResource()) }) // Set up admission plugin to auto-assign serviceaccounts to pods serviceAccountAdmission := serviceaccountadmission.NewServiceAccount(rootClientset) masterConfig := framework.NewMasterConfig() masterConfig.EnableIndex = true masterConfig.Authenticator = authenticator masterConfig.Authorizer = authorizer masterConfig.AdmissionControl = serviceAccountAdmission // Create a master and install handlers into mux. m, err := master.New(masterConfig) if err != nil { t.Fatalf("Error in bringing up the master: %v", err) } // Start the service account and service account token controllers tokenController := serviceaccountcontroller.NewTokensController(rootClientset, serviceaccountcontroller.TokensControllerOptions{TokenGenerator: serviceaccount.JWTTokenGenerator(serviceAccountKey)}) tokenController.Run() serviceAccountController := serviceaccountcontroller.NewServiceAccountsController(rootClientset, serviceaccountcontroller.DefaultServiceAccountsControllerOptions()) serviceAccountController.Run() // Start the admission plugin reflectors serviceAccountAdmission.Run() stop := func() { tokenController.Stop() serviceAccountController.Stop() serviceAccountAdmission.Stop() // TODO: Uncomment when fix #19254 // apiServer.Close() } return rootClientset, clientConfig, stop }
// 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 }
func TestTokenGenerateAndValidate(t *testing.T) { expectedUserName := "******" expectedUserUID := "12345" // Related API objects serviceAccount := &api.ServiceAccount{ ObjectMeta: api.ObjectMeta{ Name: "my-service-account", UID: "12345", Namespace: "test", }, } rsaSecret := &api.Secret{ ObjectMeta: api.ObjectMeta{ Name: "my-rsa-secret", Namespace: "test", }, } ecdsaSecret := &api.Secret{ ObjectMeta: api.ObjectMeta{ Name: "my-ecdsa-secret", Namespace: "test", }, } // Generate the RSA token rsaGenerator := serviceaccount.JWTTokenGenerator(getPrivateKey(rsaPrivateKey)) rsaToken, err := rsaGenerator.GenerateToken(*serviceAccount, *rsaSecret) if err != nil { t.Fatalf("error generating token: %v", err) } if len(rsaToken) == 0 { t.Fatalf("no token generated") } rsaSecret.Data = map[string][]byte{ "token": []byte(rsaToken), } // Generate the ECDSA token ecdsaGenerator := serviceaccount.JWTTokenGenerator(getPrivateKey(ecdsaPrivateKey)) ecdsaToken, err := ecdsaGenerator.GenerateToken(*serviceAccount, *ecdsaSecret) if err != nil { t.Fatalf("error generating token: %v", err) } if len(ecdsaToken) == 0 { t.Fatalf("no token generated") } ecdsaSecret.Data = map[string][]byte{ "token": []byte(ecdsaToken), } testCases := map[string]struct { Client clientset.Interface Keys []interface{} Token string ExpectedErr bool ExpectedOK bool ExpectedUserName string ExpectedUserUID string ExpectedGroups []string }{ "no keys": { Token: rsaToken, Client: nil, Keys: []interface{}{}, ExpectedErr: false, ExpectedOK: false, }, "invalid keys (rsa)": { Token: rsaToken, Client: nil, Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(ecdsaPublicKey)}, ExpectedErr: true, ExpectedOK: false, }, "invalid keys (ecdsa)": { Token: ecdsaToken, Client: nil, Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(rsaPublicKey)}, ExpectedErr: true, ExpectedOK: false, }, "valid key (rsa)": { Token: rsaToken, Client: nil, Keys: []interface{}{getPublicKey(rsaPublicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "valid key (ecdsa)": { Token: ecdsaToken, Client: nil, Keys: []interface{}{getPublicKey(ecdsaPublicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "rotated keys (rsa)": { Token: rsaToken, Client: nil, Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(ecdsaPublicKey), getPublicKey(rsaPublicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "rotated keys (ecdsa)": { Token: ecdsaToken, Client: nil, Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(rsaPublicKey), getPublicKey(ecdsaPublicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "valid lookup": { Token: rsaToken, Client: fake.NewSimpleClientset(serviceAccount, rsaSecret, ecdsaSecret), Keys: []interface{}{getPublicKey(rsaPublicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "invalid secret lookup": { Token: rsaToken, Client: fake.NewSimpleClientset(serviceAccount), Keys: []interface{}{getPublicKey(rsaPublicKey)}, ExpectedErr: true, ExpectedOK: false, }, "invalid serviceaccount lookup": { Token: rsaToken, Client: fake.NewSimpleClientset(rsaSecret, ecdsaSecret), Keys: []interface{}{getPublicKey(rsaPublicKey)}, ExpectedErr: true, ExpectedOK: false, }, } for k, tc := range testCases { getter := serviceaccountcontroller.NewGetterFromClient(tc.Client) authenticator := serviceaccount.JWTTokenAuthenticator(tc.Keys, tc.Client != nil, getter) // An invalid, non-JWT token should always fail if _, ok, err := authenticator.AuthenticateToken("invalid token"); err != nil || ok { t.Errorf("%s: Expected err=nil, ok=false for non-JWT token", k) continue } user, ok, err := authenticator.AuthenticateToken(tc.Token) if (err != nil) != tc.ExpectedErr { t.Errorf("%s: Expected error=%v, got %v", k, tc.ExpectedErr, err) continue } if ok != tc.ExpectedOK { t.Errorf("%s: Expected ok=%v, got %v", k, tc.ExpectedOK, ok) continue } if err != nil || !ok { continue } if user.GetName() != tc.ExpectedUserName { t.Errorf("%s: Expected username=%v, got %v", k, tc.ExpectedUserName, user.GetName()) continue } if user.GetUID() != tc.ExpectedUserUID { t.Errorf("%s: Expected userUID=%v, got %v", k, tc.ExpectedUserUID, user.GetUID()) continue } if !reflect.DeepEqual(user.GetGroups(), tc.ExpectedGroups) { t.Errorf("%s: Expected groups=%v, got %v", k, tc.ExpectedGroups, user.GetGroups()) continue } } }
func TestTokenGenerateAndValidate(t *testing.T) { expectedUserName := "******" expectedUserUID := "12345" // Related API objects serviceAccount := &api.ServiceAccount{ ObjectMeta: api.ObjectMeta{ Name: "my-service-account", UID: "12345", Namespace: "test", }, } secret := &api.Secret{ ObjectMeta: api.ObjectMeta{ Name: "my-secret", Namespace: "test", }, } // Generate the token generator := serviceaccount.JWTTokenGenerator(getPrivateKey(privateKey)) token, err := generator.GenerateToken(*serviceAccount, *secret) if err != nil { t.Fatalf("error generating token: %v", err) } if len(token) == 0 { t.Fatalf("no token generated") } // "Save" the token secret.Data = map[string][]byte{ "token": []byte(token), } testCases := map[string]struct { Client clientset.Interface Keys []*rsa.PublicKey ExpectedErr bool ExpectedOK bool ExpectedUserName string ExpectedUserUID string ExpectedGroups []string }{ "no keys": { Client: nil, Keys: []*rsa.PublicKey{}, ExpectedErr: false, ExpectedOK: false, }, "invalid keys": { Client: nil, Keys: []*rsa.PublicKey{getPublicKey(otherPublicKey)}, ExpectedErr: true, ExpectedOK: false, }, "valid key": { Client: nil, Keys: []*rsa.PublicKey{getPublicKey(publicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "rotated keys": { Client: nil, Keys: []*rsa.PublicKey{getPublicKey(otherPublicKey), getPublicKey(publicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "valid lookup": { Client: fake.NewSimpleClientset(serviceAccount, secret), Keys: []*rsa.PublicKey{getPublicKey(publicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "invalid secret lookup": { Client: fake.NewSimpleClientset(serviceAccount), Keys: []*rsa.PublicKey{getPublicKey(publicKey)}, ExpectedErr: true, ExpectedOK: false, }, "invalid serviceaccount lookup": { Client: fake.NewSimpleClientset(secret), Keys: []*rsa.PublicKey{getPublicKey(publicKey)}, ExpectedErr: true, ExpectedOK: false, }, } for k, tc := range testCases { getter := serviceaccountcontroller.NewGetterFromClient(tc.Client) authenticator := serviceaccount.JWTTokenAuthenticator(tc.Keys, tc.Client != nil, getter) user, ok, err := authenticator.AuthenticateToken(token) if (err != nil) != tc.ExpectedErr { t.Errorf("%s: Expected error=%v, got %v", k, tc.ExpectedErr, err) continue } if ok != tc.ExpectedOK { t.Errorf("%s: Expected ok=%v, got %v", k, tc.ExpectedOK, ok) continue } if err != nil || !ok { continue } if user.GetName() != tc.ExpectedUserName { t.Errorf("%s: Expected username=%v, got %v", k, tc.ExpectedUserName, user.GetName()) continue } if user.GetUID() != tc.ExpectedUserUID { t.Errorf("%s: Expected userUID=%v, got %v", k, tc.ExpectedUserUID, user.GetUID()) continue } if !reflect.DeepEqual(user.GetGroups(), tc.ExpectedGroups) { t.Errorf("%s: Expected groups=%v, got %v", k, tc.ExpectedGroups, user.GetGroups()) continue } } }