Example #1
0
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)
		}
	}
}
Example #2
0
// 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
}
Example #3
0
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
}