func TestMakeSplitUsername(t *testing.T) { username := apiserverserviceaccount.MakeUsername("ns", "name") ns, name, err := apiserverserviceaccount.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 := apiserverserviceaccount.SplitUsername("test") if err == nil { t.Errorf("Expected error for %s", n) } } }
// 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 (j *jwtTokenAuthenticator) AuthenticateToken(token string) (user.Info, bool, error) { var validationError error for i, key := range j.keys { // Attempt to verify with each key until we find one that works parsedToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { switch token.Method.(type) { case *jwt.SigningMethodRSA: if _, ok := key.(*rsa.PublicKey); ok { return key, nil } return nil, errMismatchedSigningMethod case *jwt.SigningMethodECDSA: if _, ok := key.(*ecdsa.PublicKey); ok { return key, nil } return nil, errMismatchedSigningMethod default: return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } }) if err != nil { switch err := err.(type) { case *jwt.ValidationError: if (err.Errors & jwt.ValidationErrorMalformed) != 0 { // Not a JWT, no point in continuing return nil, false, nil } if (err.Errors & jwt.ValidationErrorSignatureInvalid) != 0 { // Signature error, perhaps one of the other keys will verify the signature // If not, we want to return this error glog.V(4).Infof("Signature error (key %d): %v", i, err) validationError = err continue } // This key doesn't apply to the given signature type // Perhaps one of the other keys will verify the signature // If not, we want to return this error if err.Inner == errMismatchedSigningMethod { glog.V(4).Infof("Mismatched key type (key %d): %v", i, err) validationError = err continue } } // Other errors should just return as errors return nil, false, err } // If we get here, we have a token with a recognized signature claims, _ := parsedToken.Claims.(jwt.MapClaims) // Make sure we issued the token iss, _ := claims[IssuerClaim].(string) if iss != Issuer { return nil, false, nil } // Make sure the claims we need exist sub, _ := claims[SubjectClaim].(string) if len(sub) == 0 { return nil, false, errors.New("sub claim is missing") } namespace, _ := claims[NamespaceClaim].(string) if len(namespace) == 0 { return nil, false, errors.New("namespace claim is missing") } secretName, _ := claims[SecretNameClaim].(string) if len(namespace) == 0 { return nil, false, errors.New("secretName claim is missing") } serviceAccountName, _ := claims[ServiceAccountNameClaim].(string) if len(serviceAccountName) == 0 { return nil, false, errors.New("serviceAccountName claim is missing") } serviceAccountUID, _ := claims[ServiceAccountUIDClaim].(string) if len(serviceAccountUID) == 0 { return nil, false, errors.New("serviceAccountUID claim is missing") } subjectNamespace, subjectName, err := apiserverserviceaccount.SplitUsername(sub) if err != nil || subjectNamespace != namespace || subjectName != serviceAccountName { return nil, false, errors.New("sub claim is invalid") } if j.lookup { // Make sure token hasn't been invalidated by deletion of the secret secret, err := j.getter.GetSecret(namespace, secretName) if err != nil { glog.V(4).Infof("Could not retrieve token %s/%s for service account %s/%s: %v", namespace, secretName, namespace, serviceAccountName, err) return nil, false, errors.New("Token has been invalidated") } if bytes.Compare(secret.Data[v1.ServiceAccountTokenKey], []byte(token)) != 0 { glog.V(4).Infof("Token contents no longer matches %s/%s for service account %s/%s", namespace, secretName, namespace, serviceAccountName) return nil, false, errors.New("Token does not match server's copy") } // Make sure service account still exists (name and UID) serviceAccount, err := j.getter.GetServiceAccount(namespace, serviceAccountName) if err != nil { glog.V(4).Infof("Could not retrieve service account %s/%s: %v", namespace, serviceAccountName, err) return nil, false, err } if string(serviceAccount.UID) != serviceAccountUID { glog.V(4).Infof("Service account UID no longer matches %s/%s: %q != %q", namespace, serviceAccountName, string(serviceAccount.UID), serviceAccountUID) return nil, false, fmt.Errorf("ServiceAccount UID (%s) does not match claim (%s)", serviceAccount.UID, serviceAccountUID) } } return UserInfo(namespace, serviceAccountName, serviceAccountUID), true, nil } return nil, false, validationError }
// 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: &api.Registry.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: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}, BearerToken: rootToken}) internalRootClientset := internalclientset.NewForConfigOrDie(&restclient.Config{Host: apiServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.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 := serviceaccountapiserver.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() serviceAccountAdmission.SetInternalClientSet(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 }