// GrantNeeded implements the GrantHandler interface func (g *serviceAccountAwareGrant) GrantNeeded(user user.Info, grant *api.Grant, w http.ResponseWriter, req *http.Request) (bool, bool, error) { if _, _, err := serviceaccount.SplitUsername(grant.Client.GetId()); err == nil { return g.saClientGrantHandler.GrantNeeded(user, grant, w, req) } return g.standardGrantHandler.GrantNeeded(user, grant, w, req) }
func BuildSubjects(users, groups []string, userNameValidator, groupNameValidator validation.ValidateNameFunc) []kapi.ObjectReference { subjects := []kapi.ObjectReference{} for _, user := range users { saNamespace, saName, err := serviceaccount.SplitUsername(user) if err == nil { subjects = append(subjects, kapi.ObjectReference{Kind: ServiceAccountKind, Namespace: saNamespace, Name: saName}) continue } kind := UserKind if len(userNameValidator(user, false)) != 0 { kind = SystemUserKind } subjects = append(subjects, kapi.ObjectReference{Kind: kind, Name: user}) } for _, group := range groups { kind := GroupKind if len(groupNameValidator(group, false)) != 0 { kind = SystemGroupKind } subjects = append(subjects, kapi.ObjectReference{Kind: kind, Name: group}) } return subjects }
func WithActingAs(handler http.Handler, requestContextMapper api.RequestContextMapper, a authorizer.Authorizer) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { requestedSubject := req.Header.Get("Impersonate-User") if len(requestedSubject) == 0 { handler.ServeHTTP(w, req) return } ctx, exists := requestContextMapper.Get(req) if !exists { forbidden(w, req) return } requestor, exists := api.UserFrom(ctx) if !exists { forbidden(w, req) return } actingAsAttributes := &authorizer.AttributesRecord{ User: requestor, Verb: "impersonate", APIGroup: api.GroupName, Resource: "users", // ResourceName: requestedSubject, ResourceRequest: true, } err := a.Authorize(actingAsAttributes) if err != nil { forbidden(w, req) return } switch { case strings.HasPrefix(requestedSubject, serviceaccount.ServiceAccountUsernamePrefix): namespace, name, err := serviceaccount.SplitUsername(requestedSubject) if err != nil { forbidden(w, req) return } requestContextMapper.Update(req, api.WithUser(ctx, serviceaccount.UserInfo(namespace, name, ""))) default: newUser := &user.DefaultInfo{ Name: requestedSubject, } requestContextMapper.Update(req, api.WithUser(ctx, newUser)) } newCtx, _ := requestContextMapper.Get(req) oldUser, _ := api.UserFrom(ctx) newUser, _ := api.UserFrom(newCtx) httplog.LogOf(req, w).Addf("%v is acting as %v", oldUser, newUser) handler.ServeHTTP(w, req) }) }
func TestMakeSplitUsername(t *testing.T) { username := serviceaccount.MakeUsername("ns", "name") ns, name, err := serviceaccount.SplitUsername(username) if err != nil { t.Errorf("Unexpected error %v", err) } if ns != "ns" || name != "name" { t.Errorf("Expected ns/name, got %s/%s", ns, name) } invalid := []string{"test", "system:serviceaccount", "system:serviceaccount:", "system:serviceaccount:ns", "system:serviceaccount:ns:name:extra"} for _, n := range invalid { _, _, err := serviceaccount.SplitUsername("test") if err == nil { t.Errorf("Expected error for %s", n) } } }
func ValidateClientNameField(value string, fldPath *field.Path) field.ErrorList { if len(value) == 0 { return field.ErrorList{field.Required(fldPath, "")} } else if _, saName, err := serviceaccount.SplitUsername(value); err == nil { if reasons := validation.ValidateServiceAccountName(saName, false); len(reasons) != 0 { return field.ErrorList{field.Invalid(fldPath, value, strings.Join(reasons, ", "))} } } else if reasons := validation.NameIsDNSSubdomain(value, false); len(reasons) != 0 { return field.ErrorList{field.Invalid(fldPath, value, strings.Join(reasons, ", "))} } return field.ErrorList{} }
func ValidateClientNameField(value string, fldPath *field.Path) field.ErrorList { if len(value) == 0 { return field.ErrorList{field.Required(fldPath, "")} } else if _, saName, err := serviceaccount.SplitUsername(value); err == nil { if ok, errString := validation.ValidateServiceAccountName(saName, false); !ok { return field.ErrorList{field.Invalid(fldPath, value, errString)} } } else if ok, msg := validation.NameIsDNSSubdomain(value, false); !ok { return field.ErrorList{field.Invalid(fldPath, value, msg)} } return field.ErrorList{} }
// buildImpersonationRequests returns a list of objectreferences that represent the different things we're requesting to impersonate. // Also includes a map[string][]string representing user.Info.Extra // Each request must be authorized against the current user before switching contexts. func buildImpersonationRequests(headers http.Header) ([]api.ObjectReference, error) { impersonationRequests := []api.ObjectReference{} requestedUser := headers.Get(authenticationapi.ImpersonateUserHeader) hasUser := len(requestedUser) > 0 if hasUser { if namespace, name, err := serviceaccount.SplitUsername(requestedUser); err == nil { impersonationRequests = append(impersonationRequests, api.ObjectReference{Kind: "ServiceAccount", Namespace: namespace, Name: name}) } else { impersonationRequests = append(impersonationRequests, api.ObjectReference{Kind: "User", Name: requestedUser}) } } hasGroups := false for _, group := range headers[authenticationapi.ImpersonateGroupHeader] { hasGroups = true impersonationRequests = append(impersonationRequests, api.ObjectReference{Kind: "Group", Name: group}) } hasUserExtra := false for headerName, values := range headers { if !strings.HasPrefix(headerName, authenticationapi.ImpersonateUserExtraHeaderPrefix) { continue } hasUserExtra = true extraKey := strings.ToLower(headerName[len(authenticationapi.ImpersonateUserExtraHeaderPrefix):]) // make a separate request for each extra value they're trying to set for _, value := range values { impersonationRequests = append(impersonationRequests, api.ObjectReference{ Kind: "UserExtra", // we only parse out a group above, but the parsing will fail if there isn't SOME version // using the internal version will help us fail if anyone starts using it APIVersion: authenticationapi.SchemeGroupVersion.String(), Name: value, // ObjectReference doesn't have a subresource field. FieldPath is close and available, so we'll use that // TODO fight the good fight for ObjectReference to refer to resources and subresources FieldPath: extraKey, }) } } if (hasGroups || hasUserExtra) && !hasUser { return nil, fmt.Errorf("requested %v without impersonating a user", impersonationRequests) } return impersonationRequests, nil }
func (a *saOAuthClientAdapter) GetClient(ctx kapi.Context, name string) (*oauthapi.OAuthClient, error) { saNamespace, saName, err := serviceaccount.SplitUsername(name) if err != nil { return a.delegate.GetClient(ctx, name) } sa, err := a.saClient.ServiceAccounts(saNamespace).Get(saName) if err != nil { return nil, err } redirectURIs := []string{} if modelsMap := parseModelsMap(sa.Annotations, a.decoder); len(modelsMap) > 0 { if uris := a.extractRedirectURIs(modelsMap, saNamespace); len(uris) > 0 { redirectURIs = append(redirectURIs, uris.extractValidRedirectURIStrings()...) } } if len(redirectURIs) == 0 { return nil, fmt.Errorf( "%v has no redirectURIs; set %v<some-value>=<redirect> or create a dynamic URI using %v<some-value>=<reference>", name, OAuthRedirectModelAnnotationURIPrefix, OAuthRedirectModelAnnotationReferencePrefix, ) } tokens, err := a.getServiceAccountTokens(sa) if err != nil { return nil, err } if len(tokens) == 0 { return nil, fmt.Errorf("%v has no tokens", name) } saWantsChallenges, _ := strconv.ParseBool(sa.Annotations[OAuthWantChallengesAnnotationPrefix]) saClient := &oauthapi.OAuthClient{ ObjectMeta: kapi.ObjectMeta{Name: name}, ScopeRestrictions: getScopeRestrictionsFor(saNamespace, saName), AdditionalSecrets: tokens, RespondWithChallenges: saWantsChallenges, // TODO update this to allow https redirection to any // 1. service IP (useless in general) // 2. service DNS (useless in general) // 3. loopback? (useful, but maybe a bit weird) RedirectURIs: sets.NewString(redirectURIs...).List(), GrantMethod: a.grantMethod, } return saClient, nil }
func (a *saOAuthClientAdapter) GetClient(ctx kapi.Context, name string) (*oauthapi.OAuthClient, error) { saNamespace, saName, err := serviceaccount.SplitUsername(name) if err != nil { return a.delegate.GetClient(ctx, name) } sa, err := a.saClient.ServiceAccounts(saNamespace).Get(saName) if err != nil { return nil, err } redirectURIs := []string{} for key, value := range sa.Annotations { if strings.HasPrefix(key, OAuthRedirectURISecretAnnotationPrefix) { redirectURIs = append(redirectURIs, value) } } if len(redirectURIs) == 0 { return nil, fmt.Errorf("%v has no redirectURIs; set %v<some-value>=<redirect>", name, OAuthRedirectURISecretAnnotationPrefix) } tokens, err := a.getServiceAccountTokens(sa) if err != nil { return nil, err } if len(tokens) == 0 { return nil, fmt.Errorf("%v has no tokens", name) } saWantsChallenges, _ := strconv.ParseBool(sa.Annotations[OAuthWantChallengesAnnotationPrefix]) saClient := &oauthapi.OAuthClient{ ObjectMeta: kapi.ObjectMeta{Name: name}, ScopeRestrictions: getScopeRestrictionsFor(saNamespace, saName), AdditionalSecrets: tokens, RespondWithChallenges: saWantsChallenges, // TODO update this to allow https redirection to any // 1. service IP (useless in general) // 2. service DNS (useless in general) // 3. route DNS (useful) // 4. loopback? (useful, but maybe a bit weird) RedirectURIs: redirectURIs, GrantMethod: a.grantMethod, } return saClient, nil }
// maxProjectsByRequester returns the maximum number of projects allowed for a given user, whether a limit exists, and an error // if an error occurred. If a limit doesn't exist, the maximum number should be ignored. func (o *projectRequestLimit) maxProjectsByRequester(userName string) (int, bool, error) { // service accounts have a different ruleset, check them if _, _, err := serviceaccount.SplitUsername(userName); err == nil { if o.config.MaxProjectsForServiceAccounts == nil { return 0, false, nil } return *o.config.MaxProjectsForServiceAccounts, true, nil } // if we aren't a valid username, we came in as cert user for certain, use our cert user rules if valid, _ := uservalidation.ValidateUserName(userName, false); !valid { if o.config.MaxProjectsForSystemUsers == nil { return 0, false, nil } return *o.config.MaxProjectsForSystemUsers, true, nil } // prevent a user lookup if no limits are configured if len(o.config.Limits) == 0 { return 0, false, nil } user, err := o.client.Users().Get(userName) if err != nil { return 0, false, err } userLabels := labels.Set(user.Labels) for _, limit := range o.config.Limits { selector := labels.Set(limit.Selector).AsSelector() if selector.Matches(userLabels) { if limit.MaxProjects == nil { return 0, false, nil } return *limit.MaxProjects, true, nil } } return 0, false, nil }
// 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 (l *Grant) handleForm(user user.Info, w http.ResponseWriter, req *http.Request) { q := req.URL.Query() then := q.Get(thenParam) clientID := q.Get(clientIDParam) scopes := scope.Split(q.Get(scopeParam)) redirectURI := q.Get(redirectURIParam) client, err := l.clientregistry.GetClient(kapi.NewContext(), clientID) if err != nil || client == nil { l.failed("Could not find client for client_id", w, req) return } if err := scopeauthorizer.ValidateScopeRestrictions(client, scopes...); err != nil { failure := fmt.Sprintf("%v requested illegal scopes (%v): %v", client.Name, scopes, err) l.failed(failure, w, req) return } uri, err := getBaseURL(req) if err != nil { glog.Errorf("Unable to generate base URL: %v", err) http.Error(w, "Unable to determine URL", http.StatusInternalServerError) return } csrf, err := l.csrf.Generate(w, req) if err != nil { glog.Errorf("Unable to generate CSRF token: %v", err) l.failed("Could not generate CSRF token", w, req) return } grantedScopeNames := []string{} grantedScopes := []Scope{} requestedScopes := []Scope{} clientAuthID := l.authregistry.ClientAuthorizationName(user.GetName(), client.Name) if clientAuth, err := l.authregistry.GetClientAuthorization(kapi.NewContext(), clientAuthID); err == nil { grantedScopeNames = clientAuth.Scopes } for _, s := range scopes { requestedScopes = append(requestedScopes, getScopeData(s, grantedScopeNames)) } for _, s := range grantedScopeNames { grantedScopes = append(grantedScopes, getScopeData(s, grantedScopeNames)) } form := Form{ Action: uri.String(), GrantedScopes: grantedScopes, Names: GrantFormFields{ Then: thenParam, CSRF: csrfParam, ClientID: clientIDParam, UserName: userNameParam, Scopes: scopeParam, RedirectURI: redirectURIParam, Approve: approveParam, Deny: denyParam, }, Values: GrantFormFields{ Then: then, CSRF: csrf, ClientID: client.Name, UserName: user.GetName(), Scopes: requestedScopes, RedirectURI: redirectURI, }, } if saNamespace, saName, err := serviceaccount.SplitUsername(client.Name); err == nil { form.ServiceAccountName = saName form.ServiceAccountNamespace = saNamespace } l.render.Render(form, w, req) }