// UserInfo returns a user.Info interface for the given namespace, service account name and UID func UserInfo(namespace, name, uid string) user.Info { return &user.DefaultInfo{ Name: apiserverserviceaccount.MakeUsername(namespace, name), UID: uid, Groups: apiserverserviceaccount.MakeGroupNames(namespace, name), } }
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) } } }
func (j *jwtTokenGenerator) GenerateToken(serviceAccount v1.ServiceAccount, secret v1.Secret) (string, error) { var method jwt.SigningMethod switch privateKey := j.privateKey.(type) { case *rsa.PrivateKey: method = jwt.SigningMethodRS256 case *ecdsa.PrivateKey: switch privateKey.Curve { case elliptic.P256(): method = jwt.SigningMethodES256 case elliptic.P384(): method = jwt.SigningMethodES384 case elliptic.P521(): method = jwt.SigningMethodES512 default: return "", fmt.Errorf("unknown private key curve, must be 256, 384, or 521") } default: return "", fmt.Errorf("unknown private key type %T, must be *rsa.PrivateKey or *ecdsa.PrivateKey", j.privateKey) } token := jwt.New(method) claims, _ := token.Claims.(jwt.MapClaims) // Identify the issuer claims[IssuerClaim] = Issuer // Username claims[SubjectClaim] = apiserverserviceaccount.MakeUsername(serviceAccount.Namespace, serviceAccount.Name) // Persist enough structured info for the authenticator to be able to look up the service account and secret claims[NamespaceClaim] = serviceAccount.Namespace claims[ServiceAccountNameClaim] = serviceAccount.Name claims[ServiceAccountUIDClaim] = serviceAccount.UID claims[SecretNameClaim] = secret.Name // Sign and get the complete encoded token as a string return token.SignedString(j.privateKey) }
func appliesToUser(user user.Info, subject rbac.Subject, namespace string) bool { switch subject.Kind { case rbac.UserKind: return user.GetName() == subject.Name case rbac.GroupKind: return has(user.GetGroups(), subject.Name) case rbac.ServiceAccountKind: // default the namespace to namespace we're working in if its available. This allows rolebindings that reference // SAs in th local namespace to avoid having to qualify them. saNamespace := namespace if len(subject.Namespace) > 0 { saNamespace = subject.Namespace } if len(saNamespace) == 0 { return false } return serviceaccount.MakeUsername(saNamespace, subject.Name) == user.GetName() default: return false } }
conformanceTests []conformanceTests ) f := framework.NewDefaultFramework("ingress") BeforeEach(func() { f.BeforeEach() jig = newTestJig(f.ClientSet) ns = f.Namespace.Name // this test wants powerful permissions. Since the namespace names are unique, we can leave this // lying around so we don't have to race any caches framework.BindClusterRole(jig.client.Rbac(), "cluster-admin", f.Namespace.Name, rbacv1beta1.Subject{Kind: rbacv1beta1.ServiceAccountKind, Namespace: f.Namespace.Name, Name: "default"}) err := framework.WaitForAuthorizationUpdate(jig.client.Authorization(), serviceaccount.MakeUsername(f.Namespace.Name, "default"), "", "create", schema.GroupResource{Resource: "pods"}, true) framework.ExpectNoError(err) }) // Before enabling this loadbalancer test in any other test list you must // make sure the associated project has enough quota. At the time of this // writing a GCE project is allowed 3 backend services by default. This // test requires at least 5. // // Slow by design ~10m for each "It" block dominated by loadbalancer setup time // TODO: write similar tests for nginx, haproxy and AWS Ingress. framework.KubeDescribe("GCE [Slow] [Feature:Ingress]", func() { var gceController *GCEIngressController // Platform specific setup
// config returns a complete clientConfig for constructing clients. This is separate in anticipation of composition // which means that not all clientsets are known here func (b SAControllerClientBuilder) Config(name string) (*restclient.Config, error) { clientConfig := restclient.AnonymousClientConfig(b.ClientConfig) // we need the SA UID to find a matching SA token sa, err := b.CoreClient.ServiceAccounts(b.Namespace).Get(name, metav1.GetOptions{}) if err != nil && !apierrors.IsNotFound(err) { return nil, err } else if apierrors.IsNotFound(err) { // check to see if the namespace exists. If it isn't a NotFound, just try to create the SA. // It'll probably fail, but perhaps that will have a better message. if _, err := b.CoreClient.Namespaces().Get(b.Namespace, metav1.GetOptions{}); apierrors.IsNotFound(err) { _, err = b.CoreClient.Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: b.Namespace}}) if err != nil && !apierrors.IsAlreadyExists(err) { return nil, err } } sa, err = b.CoreClient.ServiceAccounts(b.Namespace).Create( &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: b.Namespace, Name: name}}) if err != nil { return nil, err } } lw := &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { options.FieldSelector = fields.SelectorFromSet(map[string]string{api.SecretTypeField: string(v1.SecretTypeServiceAccountToken)}).String() return b.CoreClient.Secrets(b.Namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { options.FieldSelector = fields.SelectorFromSet(map[string]string{api.SecretTypeField: string(v1.SecretTypeServiceAccountToken)}).String() return b.CoreClient.Secrets(b.Namespace).Watch(options) }, } _, err = cache.ListWatchUntil(30*time.Second, lw, func(event watch.Event) (bool, error) { switch event.Type { case watch.Deleted: return false, nil case watch.Error: return false, fmt.Errorf("error watching") case watch.Added, watch.Modified: secret := event.Object.(*v1.Secret) if !serviceaccount.IsServiceAccountToken(secret, sa) || len(secret.Data[v1.ServiceAccountTokenKey]) == 0 { return false, nil } // TODO maybe verify the token is valid clientConfig.BearerToken = string(secret.Data[v1.ServiceAccountTokenKey]) restclient.AddUserAgent(clientConfig, apiserverserviceaccount.MakeUsername(b.Namespace, name)) return true, nil default: return false, fmt.Errorf("unexpected event type: %v", event.Type) } }) if err != nil { return nil, fmt.Errorf("unable to get token for service account: %v", err) } return clientConfig, nil }
func TestImpersonateIsForbidden(t *testing.T) { // Set up a master masterConfig := framework.NewIntegrationTestMasterConfig() masterConfig.GenericConfig.Authenticator = getTestTokenAuth() masterConfig.GenericConfig.Authorizer = impersonateAuthorizer{} _, s := framework.RunAMaster(masterConfig) defer s.Close() ns := framework.CreateTestingNamespace("auth-impersonate-forbidden", s, t) defer framework.DeleteTestingNamespace(ns, s, t) transport := http.DefaultTransport // bob can't perform actions himself for _, r := range getTestRequests(ns.Name) { token := BobToken bodyBytes := bytes.NewReader([]byte(r.body)) req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes) if err != nil { t.Fatalf("unexpected error: %v", err) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) func() { resp, err := transport.RoundTrip(req) defer resp.Body.Close() if err != nil { t.Logf("case %v", r) t.Fatalf("unexpected error: %v", err) } // Expect all of bob's actions to return Forbidden if resp.StatusCode != http.StatusForbidden { t.Logf("case %v", r) t.Errorf("Expected not status Forbidden, but got %s", resp.Status) } }() } // bob can impersonate alice to do other things for _, r := range getTestRequests(ns.Name) { token := BobToken bodyBytes := bytes.NewReader([]byte(r.body)) req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes) if err != nil { t.Fatalf("unexpected error: %v", err) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) req.Header.Set("Impersonate-User", "alice") func() { resp, err := transport.RoundTrip(req) defer resp.Body.Close() if err != nil { t.Logf("case %v", r) t.Fatalf("unexpected error: %v", err) } // Expect all the requests to be allowed, don't care what they actually do if resp.StatusCode == http.StatusForbidden { t.Logf("case %v", r) t.Errorf("Expected status not %v, but got %v", http.StatusForbidden, resp.StatusCode) } }() } // alice can't impersonate bob for _, r := range getTestRequests(ns.Name) { token := AliceToken bodyBytes := bytes.NewReader([]byte(r.body)) req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes) if err != nil { t.Fatalf("unexpected error: %v", err) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) req.Header.Set("Impersonate-User", "bob") func() { resp, err := transport.RoundTrip(req) defer resp.Body.Close() if err != nil { t.Logf("case %v", r) t.Fatalf("unexpected error: %v", err) } // Expect all of bob's actions to return Forbidden if resp.StatusCode != http.StatusForbidden { t.Logf("case %v", r) t.Errorf("Expected not status Forbidden, but got %s", resp.Status) } }() } // alice can impersonate a service account for _, r := range getTestRequests(ns.Name) { token := BobToken bodyBytes := bytes.NewReader([]byte(r.body)) req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes) if err != nil { t.Fatalf("unexpected error: %v", err) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) req.Header.Set("Impersonate-User", serviceaccount.MakeUsername("default", "default")) func() { resp, err := transport.RoundTrip(req) defer resp.Body.Close() if err != nil { t.Logf("case %v", r) t.Fatalf("unexpected error: %v", err) } // Expect all the requests to be allowed, don't care what they actually do if resp.StatusCode == http.StatusForbidden { t.Logf("case %v", r) t.Errorf("Expected status not %v, but got %v", http.StatusForbidden, resp.StatusCode) } }() } }
// WithImpersonation is a filter that will inspect and check requests that attempt to change the user.Info for their requests func WithImpersonation(handler http.Handler, requestContextMapper request.RequestContextMapper, a authorizer.Authorizer) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { impersonationRequests, err := buildImpersonationRequests(req.Header) if err != nil { glog.V(4).Infof("%v", err) responsewriters.InternalError(w, req, err) return } if len(impersonationRequests) == 0 { handler.ServeHTTP(w, req) return } ctx, exists := requestContextMapper.Get(req) if !exists { responsewriters.InternalError(w, req, errors.New("no context found for request")) return } requestor, exists := request.UserFrom(ctx) if !exists { responsewriters.InternalError(w, req, errors.New("no user found for request")) return } // if groups are not specified, then we need to look them up differently depending on the type of user // if they are specified, then they are the authority groupsSpecified := len(req.Header[authenticationapi.ImpersonateGroupHeader]) > 0 // make sure we're allowed to impersonate each thing we're requesting. While we're iterating through, start building username // and group information username := "" groups := []string{} userExtra := map[string][]string{} for _, impersonationRequest := range impersonationRequests { actingAsAttributes := &authorizer.AttributesRecord{ User: requestor, Verb: "impersonate", APIGroup: impersonationRequest.GetObjectKind().GroupVersionKind().Group, Namespace: impersonationRequest.Namespace, Name: impersonationRequest.Name, ResourceRequest: true, } switch impersonationRequest.GetObjectKind().GroupVersionKind().GroupKind() { case api.Kind("ServiceAccount"): actingAsAttributes.Resource = "serviceaccounts" username = serviceaccount.MakeUsername(impersonationRequest.Namespace, impersonationRequest.Name) if !groupsSpecified { // if groups aren't specified for a service account, we know the groups because its a fixed mapping. Add them groups = serviceaccount.MakeGroupNames(impersonationRequest.Namespace, impersonationRequest.Name) } case api.Kind("User"): actingAsAttributes.Resource = "users" username = impersonationRequest.Name case api.Kind("Group"): actingAsAttributes.Resource = "groups" groups = append(groups, impersonationRequest.Name) case authenticationapi.Kind("UserExtra"): extraKey := impersonationRequest.FieldPath extraValue := impersonationRequest.Name actingAsAttributes.Resource = "userextras" actingAsAttributes.Subresource = extraKey userExtra[extraKey] = append(userExtra[extraKey], extraValue) default: glog.V(4).Infof("unknown impersonation request type: %v", impersonationRequest) responsewriters.Forbidden(actingAsAttributes, w, req, fmt.Sprintf("unknown impersonation request type: %v", impersonationRequest)) return } allowed, reason, err := a.Authorize(actingAsAttributes) if err != nil || !allowed { glog.V(4).Infof("Forbidden: %#v, Reason: %s, Error: %v", req.RequestURI, reason, err) responsewriters.Forbidden(actingAsAttributes, w, req, reason) return } } newUser := &user.DefaultInfo{ Name: username, Groups: groups, Extra: userExtra, } requestContextMapper.Update(req, request.WithUser(ctx, newUser)) oldUser, _ := request.UserFrom(ctx) httplog.LogOf(req, w).Addf("%v is acting as %v", oldUser, newUser) // clear all the impersonation headers from the request req.Header.Del(authenticationapi.ImpersonateUserHeader) req.Header.Del(authenticationapi.ImpersonateGroupHeader) for headerName := range req.Header { if strings.HasPrefix(headerName, authenticationapi.ImpersonateUserExtraHeaderPrefix) { req.Header.Del(headerName) } } handler.ServeHTTP(w, req) }) }