From 74f5ed6b17287100b339a2b3a43fd4c6fb200978 Mon Sep 17 00:00:00 2001 From: Margo Crawford Date: Thu, 4 Mar 2021 15:19:52 -0800 Subject: [PATCH] This introduces an Impersonate-Uid header to server side code. UserInfo contains a uid field alongside groups, username and extra. This change makes it possible to pass a UID through as an impersonation header like you can with Impersonate-Group, Impersonate-User and Impersonate-Extra. This PR contains: * Changes to impersonation.go to parse the Impersonate-Uid header and authorize uid impersonation * Unit tests for allowed and disallowed impersonation cases * An integration test that creates a CertificateSigningRequest using impersonation, and ensures that the API server populates the correct impersonated spec.uid upon creation. --- .../src/k8s.io/api/authentication/v1/types.go | 3 + .../pkg/endpoints/filters/impersonation.go | 19 +- .../endpoints/filters/impersonation_test.go | 92 ++++++++- test/integration/auth/auth_test.go | 183 ++++++++++++++++++ test/integration/authutil/authutil.go | 121 ++++++++++++ .../certificates/admission_approval_test.go | 6 +- .../certificates/admission_sign_test.go | 6 +- .../certificates/admission_test.go | 59 ------ .../certificates/controller_approval_test.go | 4 +- 9 files changed, 423 insertions(+), 70 deletions(-) create mode 100644 test/integration/authutil/authutil.go delete mode 100644 test/integration/certificates/admission_test.go diff --git a/staging/src/k8s.io/api/authentication/v1/types.go b/staging/src/k8s.io/api/authentication/v1/types.go index 6f5f0ad1a30..54f2b29cb36 100644 --- a/staging/src/k8s.io/api/authentication/v1/types.go +++ b/staging/src/k8s.io/api/authentication/v1/types.go @@ -31,6 +31,9 @@ const ( // It can be repeated multiplied times for multiple groups. ImpersonateGroupHeader = "Impersonate-Group" + // ImpersonateUIDHeader is used to impersonate a particular UID during an API server request + ImpersonateUIDHeader = "Impersonate-Uid" + // ImpersonateUserExtraHeaderPrefix is a prefix for any header used to impersonate an entry in the // extra map[string][]string for user.Info. The key will be every after the prefix. // It can be repeated multiplied times for multiple map keys and the same key can be repeated multiple diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go index 16dd180dbcf..d82ad68a7c2 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go @@ -67,6 +67,7 @@ func WithImpersonation(handler http.Handler, a authorizer.Authorizer, s runtime. username := "" groups := []string{} userExtra := map[string][]string{} + uid := "" for _, impersonationRequest := range impersonationRequests { gvk := impersonationRequest.GetObjectKind().GroupVersionKind() actingAsAttributes := &authorizer.AttributesRecord{ @@ -103,6 +104,10 @@ func WithImpersonation(handler http.Handler, a authorizer.Authorizer, s runtime. actingAsAttributes.Subresource = extraKey userExtra[extraKey] = append(userExtra[extraKey], extraValue) + case authenticationv1.SchemeGroupVersion.WithKind("UID").GroupKind(): + uid = string(impersonationRequest.Name) + actingAsAttributes.Resource = "uids" + default: klog.V(4).InfoS("unknown impersonation request type", "Request", impersonationRequest) responsewriters.Forbidden(ctx, actingAsAttributes, w, req, fmt.Sprintf("unknown impersonation request type: %v", impersonationRequest), s) @@ -154,6 +159,7 @@ func WithImpersonation(handler http.Handler, a authorizer.Authorizer, s runtime. Name: username, Groups: groups, Extra: userExtra, + UID: uid, } req = req.WithContext(request.WithUser(ctx, newUser)) @@ -166,6 +172,7 @@ func WithImpersonation(handler http.Handler, a authorizer.Authorizer, s runtime. // clear all the impersonation headers from the request req.Header.Del(authenticationv1.ImpersonateUserHeader) req.Header.Del(authenticationv1.ImpersonateGroupHeader) + req.Header.Del(authenticationv1.ImpersonateUIDHeader) for headerName := range req.Header { if strings.HasPrefix(headerName, authenticationv1.ImpersonateUserExtraHeaderPrefix) { req.Header.Del(headerName) @@ -231,7 +238,17 @@ func buildImpersonationRequests(headers http.Header) ([]v1.ObjectReference, erro } } - if (hasGroups || hasUserExtra) && !hasUser { + requestedUID := headers.Get(authenticationv1.ImpersonateUIDHeader) + hasUID := len(requestedUID) > 0 + if hasUID { + impersonationRequests = append(impersonationRequests, v1.ObjectReference{ + Kind: "UID", + Name: requestedUID, + APIVersion: authenticationv1.SchemeGroupVersion.String(), + }) + } + + if (hasGroups || hasUserExtra || hasUID) && !hasUser { return nil, fmt.Errorf("requested %v without impersonating a user", impersonationRequests) } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation_test.go index 376182b589c..2ef79f954b9 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation_test.go @@ -75,11 +75,27 @@ func (impersonateAuthorizer) Authorize(ctx context.Context, a authorizer.Attribu } if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-particular-scopes" && - a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "scopes" && a.GetName() == "scope-a" { + a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "scopes" && a.GetName() == "scope-a" && a.GetAPIGroup() == "authentication.k8s.io" { return authorizer.DecisionAllow, "", nil } - if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-project" && a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "project" { + if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-project" && a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "project" && a.GetAPIGroup() == "authentication.k8s.io" { + return authorizer.DecisionAllow, "", nil + } + + if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "everything-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "users" && a.GetAPIGroup() == "" { + return authorizer.DecisionAllow, "", nil + } + + if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "everything-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "uids" && a.GetName() == "some-uid" && a.GetAPIGroup() == "authentication.k8s.io" { + return authorizer.DecisionAllow, "", nil + } + + if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "everything-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "groups" && a.GetAPIGroup() == "" { + return authorizer.DecisionAllow, "", nil + } + + if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "everything-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "scopes" && a.GetAPIGroup() == "authentication.k8s.io" { return authorizer.DecisionAllow, "", nil } @@ -93,6 +109,7 @@ func TestImpersonationFilter(t *testing.T) { impersonationUser string impersonationGroups []string impersonationUserExtras map[string][]string + impersonationUid string expectedUser user.Info expectedCode int }{ @@ -139,6 +156,17 @@ func TestImpersonationFilter(t *testing.T) { }, expectedCode: http.StatusInternalServerError, }, + { + name: "impersonating-uid-without-user", + user: &user.DefaultInfo{ + Name: "tester", + }, + impersonationUid: "some-uid", + expectedUser: &user.DefaultInfo{ + Name: "tester", + }, + expectedCode: http.StatusInternalServerError, + }, { name: "disallowed-group", user: &user.DefaultInfo{ @@ -383,6 +411,60 @@ func TestImpersonationFilter(t *testing.T) { }, expectedCode: http.StatusOK, }, + { + name: "allowed-user-impersonation-with-uid", + user: &user.DefaultInfo{ + Name: "dev", + Groups: []string{ + "everything-impersonater", + }, + }, + impersonationUser: "tester", + impersonationUid: "some-uid", + expectedUser: &user.DefaultInfo{ + Name: "tester", + Groups: []string{"system:authenticated"}, + Extra: map[string][]string{}, + UID: "some-uid", + }, + expectedCode: http.StatusOK, + }, + { + name: "disallowed-user-impersonation-with-uid", + user: &user.DefaultInfo{ + Name: "dev", + Groups: []string{ + "everything-impersonater", + }, + }, + impersonationUser: "tester", + impersonationUid: "disallowed-uid", + expectedUser: &user.DefaultInfo{ + Name: "dev", + Groups: []string{"everything-impersonater"}, + }, + expectedCode: http.StatusForbidden, + }, + { + name: "allowed-impersonation-with-all-headers", + user: &user.DefaultInfo{ + Name: "dev", + Groups: []string{ + "everything-impersonater", + }, + }, + impersonationUser: "tester", + impersonationUid: "some-uid", + impersonationGroups: []string{"system:authenticated"}, + impersonationUserExtras: map[string][]string{"scopes": {"scope-a", "scope-b"}}, + expectedUser: &user.DefaultInfo{ + Name: "tester", + Groups: []string{"system:authenticated"}, + UID: "some-uid", + Extra: map[string][]string{"scopes": {"scope-a", "scope-b"}}, + }, + expectedCode: http.StatusOK, + }, } var ctx context.Context @@ -410,6 +492,9 @@ func TestImpersonationFilter(t *testing.T) { t.Fatalf("extra header still present: %v", key) } } + if _, ok := req.Header[authenticationapi.ImpersonateUIDHeader]; ok { + t.Fatal("uid header still present") + } }) handler := func(delegate http.Handler) http.Handler { @@ -463,6 +548,9 @@ func TestImpersonationFilter(t *testing.T) { req.Header.Add(authenticationapi.ImpersonateUserExtraHeaderPrefix+extraKey, value) } } + if len(tc.impersonationUid) > 0 { + req.Header.Add(authenticationapi.ImpersonateUIDHeader, tc.impersonationUid) + } resp, err := http.DefaultClient.Do(req) if err != nil { diff --git a/test/integration/auth/auth_test.go b/test/integration/auth/auth_test.go index 4cbf2b02a5e..f01437c4945 100644 --- a/test/integration/auth/auth_test.go +++ b/test/integration/auth/auth_test.go @@ -23,7 +23,12 @@ package auth import ( "bytes" "context" + "crypto/ed25519" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" "encoding/json" + "encoding/pem" "fmt" "io/ioutil" "net/http" @@ -35,7 +40,12 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp" + authenticationv1beta1 "k8s.io/api/authentication/v1beta1" + certificatesv1 "k8s.io/api/certificates/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilnet "k8s.io/apimachinery/pkg/util/net" "k8s.io/apimachinery/pkg/util/wait" @@ -49,15 +59,25 @@ import ( "k8s.io/apiserver/pkg/authorization/authorizerfactory" "k8s.io/apiserver/plugin/pkg/authenticator/token/tokentest" "k8s.io/apiserver/plugin/pkg/authenticator/token/webhook" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" v1 "k8s.io/client-go/tools/clientcmd/api/v1" + kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" "k8s.io/kubernetes/pkg/apis/autoscaling" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/auth/authorizer/abac" "k8s.io/kubernetes/test/integration" + "k8s.io/kubernetes/test/integration/authutil" "k8s.io/kubernetes/test/integration/framework" ) +type roundTripperFunc func(*http.Request) (*http.Response, error) + +func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req) +} + const ( AliceToken string = "abc123" // username: alice. Present in token file. BobToken string = "xyz987" // username: bob. Present in token file. @@ -875,6 +895,169 @@ func TestImpersonateIsForbidden(t *testing.T) { } +func TestImpersonateWithUID(t *testing.T) { + server := kubeapiservertesting.StartTestServerOrDie( + t, + nil, + []string{ + "--authorization-mode=RBAC", + "--anonymous-auth", + }, + framework.SharedEtcd(), + ) + t.Cleanup(server.TearDownFn) + + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) + t.Cleanup(cancel) + + setUIDWrapper := func(rt http.RoundTripper) http.RoundTripper { + return roundTripperFunc(func(req *http.Request) (*http.Response, error) { + req.Header.Set("Impersonate-Uid", "1234") + return rt.RoundTrip(req) + }) + } + + t.Run("impersonation with uid header", func(t *testing.T) { + adminClient := clientset.NewForConfigOrDie(server.ClientConfig) + + authutil.GrantUserAuthorization(t, ctx, adminClient, "alice", + rbacv1.PolicyRule{ + Verbs: []string{"create"}, + APIGroups: []string{"certificates.k8s.io"}, + Resources: []string{"certificatesigningrequests"}, + }, + ) + + req := csrPEM(t) + + clientConfig := rest.CopyConfig(server.ClientConfig) + clientConfig.Impersonate = rest.ImpersonationConfig{ + UserName: "alice", + } + clientConfig.Wrap(setUIDWrapper) + + client := clientset.NewForConfigOrDie(clientConfig) + createdCsr, err := client.CertificatesV1().CertificateSigningRequests().Create( + ctx, + &certificatesv1.CertificateSigningRequest{ + Spec: certificatesv1.CertificateSigningRequestSpec{ + SignerName: "kubernetes.io/kube-apiserver-client", + Request: req, + Usages: []certificatesv1.KeyUsage{"client auth"}, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "impersonated-csr", + }, + }, + metav1.CreateOptions{}, + ) + if err != nil { + t.Fatalf("Unexpected error creating Certificate Signing Request: %v", err) + } + + // require that all the original fields and the impersonated user's info + // is in the returned spec. + expectedCsrSpec := certificatesv1.CertificateSigningRequestSpec{ + Groups: []string{"system:authenticated"}, + SignerName: "kubernetes.io/kube-apiserver-client", + Request: req, + Usages: []certificatesv1.KeyUsage{"client auth"}, + Username: "alice", + UID: "1234", + } + actualCsrSpec := createdCsr.Spec + + if diff := cmp.Diff(expectedCsrSpec, actualCsrSpec); diff != "" { + t.Fatalf("CSR spec was different than expected, -got, +want:\n %s", diff) + } + }) + + t.Run("impersonation with only UID fails", func(t *testing.T) { + clientConfig := rest.CopyConfig(server.ClientConfig) + clientConfig.Wrap(setUIDWrapper) + + client := clientset.NewForConfigOrDie(clientConfig) + _, err := client.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) + + if !errors.IsInternalError(err) { + t.Fatalf("expected internal error, got %T %v", err, err) + } + if diff := cmp.Diff( + `an error on the server ("Internal Server Error: \"/api/v1/nodes\": `+ + `requested [{UID 1234 authentication.k8s.io/v1 }] without impersonating a user") `+ + `has prevented the request from succeeding (get nodes)`, + err.Error(), + ); diff != "" { + t.Fatalf("internal error different than expected, -got, +want:\n %s", diff) + } + }) + + t.Run("impersonating UID without authorization fails", func(t *testing.T) { + adminClient := clientset.NewForConfigOrDie(server.ClientConfig) + + authutil.GrantUserAuthorization(t, ctx, adminClient, "system:anonymous", + rbacv1.PolicyRule{ + Verbs: []string{"impersonate"}, + APIGroups: []string{""}, + Resources: []string{"users"}, + ResourceNames: []string{"some-user-anonymous-can-impersonate"}, + }, + ) + + clientConfig := rest.AnonymousClientConfig(server.ClientConfig) + clientConfig.Impersonate = rest.ImpersonationConfig{ + UserName: "some-user-anonymous-can-impersonate", + } + clientConfig.Wrap(setUIDWrapper) + + client := clientset.NewForConfigOrDie(clientConfig) + _, err := client.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) + + if !errors.IsForbidden(err) { + t.Fatalf("expected forbidden error, got %T %v", err, err) + } + if diff := cmp.Diff( + `uids.authentication.k8s.io "1234" is forbidden: `+ + `User "system:anonymous" cannot impersonate resource "uids" in API group "authentication.k8s.io" at the cluster scope`, + err.Error(), + ); diff != "" { + t.Fatalf("forbidden error different than expected, -got, +want:\n %s", diff) + } + }) +} + +func csrPEM(t *testing.T) []byte { + t.Helper() + + _, privateKey, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("Unexpected error generating ed25519 key: %v", err) + } + + csrDER, err := x509.CreateCertificateRequest( + rand.Reader, + &x509.CertificateRequest{ + Subject: pkix.Name{ + Organization: []string{}, + }, + }, + privateKey) + if err != nil { + t.Fatalf("Unexpected error creating x509 certificate request: %v", err) + } + + csrPemBlock := &pem.Block{ + Type: "CERTIFICATE REQUEST", + Bytes: csrDER, + } + + req := pem.EncodeToMemory(csrPemBlock) + if req == nil { + t.Fatalf("Failed to encode PEM to memory.") + } + return req +} + func newAuthorizerWithContents(t *testing.T, contents string) authorizer.Authorizer { f, err := ioutil.TempFile("", "auth_test") if err != nil { diff --git a/test/integration/authutil/authutil.go b/test/integration/authutil/authutil.go new file mode 100644 index 00000000000..e605ab201d1 --- /dev/null +++ b/test/integration/authutil/authutil.go @@ -0,0 +1,121 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package authutil + +import ( + "context" + "strings" + "testing" + "time" + + authorizationv1 "k8s.io/api/authorization/v1" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/wait" + clientset "k8s.io/client-go/kubernetes" + authorizationv1client "k8s.io/client-go/kubernetes/typed/authorization/v1" +) + +// WaitForNamedAuthorizationUpdate checks if the given user can perform the named verb and action on the named resource. +// Copied from k8s.io/kubernetes/test/e2e/framework/auth. +func WaitForNamedAuthorizationUpdate(t *testing.T, ctx context.Context, c authorizationv1client.SubjectAccessReviewsGetter, user, namespace, verb, resourceName string, resource schema.GroupResource, allowed bool) { + t.Helper() + + review := &authorizationv1.SubjectAccessReview{ + Spec: authorizationv1.SubjectAccessReviewSpec{ + ResourceAttributes: &authorizationv1.ResourceAttributes{ + Group: resource.Group, + Verb: verb, + Resource: resource.Resource, + Namespace: namespace, + Name: resourceName, + }, + User: user, + }, + } + + if err := wait.Poll(200*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) { + response, err := c.SubjectAccessReviews().Create(ctx, review, metav1.CreateOptions{}) + if err != nil { + return false, err + } + if response.Status.Allowed != allowed { + return false, nil + } + return true, nil + }); err != nil { + t.Fatal(err) + } +} + +func GrantUserAuthorization(t *testing.T, ctx context.Context, adminClient clientset.Interface, username string, rule rbacv1.PolicyRule) { + t.Helper() + + cr, err := adminClient.RbacV1().ClusterRoles().Create(ctx, &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{GenerateName: strings.Replace(t.Name(), "/", "--", -1)}, + Rules: []rbacv1.PolicyRule{ + rule, + }, + }, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + _ = adminClient.RbacV1().ClusterRoles().Delete(ctx, cr.Name, metav1.DeleteOptions{}) + }) + + crb, err := adminClient.RbacV1().ClusterRoleBindings().Create(ctx, &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{GenerateName: strings.Replace(t.Name(), "/", "--", -1)}, + Subjects: []rbacv1.Subject{ + { + Kind: rbacv1.UserKind, + APIGroup: rbacv1.GroupName, + Name: username, + Namespace: "", + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "ClusterRole", + Name: cr.Name, + }, + }, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + _ = adminClient.RbacV1().ClusterRoleBindings().Delete(ctx, crb.Name, metav1.DeleteOptions{}) + }) + + var resourceName string + if len(rule.ResourceNames) > 0 { + resourceName = rule.ResourceNames[0] + } + + WaitForNamedAuthorizationUpdate( + t, + ctx, + adminClient.AuthorizationV1(), + username, + "", + rule.Verbs[0], + resourceName, + schema.GroupResource{Group: rule.APIGroups[0], Resource: rule.Resources[0]}, + true, + ) +} diff --git a/test/integration/certificates/admission_approval_test.go b/test/integration/certificates/admission_approval_test.go index d281abbc62e..672e4ae424e 100644 --- a/test/integration/certificates/admission_approval_test.go +++ b/test/integration/certificates/admission_approval_test.go @@ -27,8 +27,8 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" clientset "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" - kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" + "k8s.io/kubernetes/test/integration/authutil" "k8s.io/kubernetes/test/integration/framework" ) @@ -102,8 +102,8 @@ func grantUserPermissionToApproveFor(t *testing.T, client clientset.Interface, u } approveRule := cr.Rules[0] updateRule := cr.Rules[1] - waitForNamedAuthorizationUpdate(t, client.AuthorizationV1(), username, "", approveRule.Verbs[0], approveRule.ResourceNames[0], schema.GroupResource{Group: approveRule.APIGroups[0], Resource: approveRule.Resources[0]}, true) - waitForNamedAuthorizationUpdate(t, client.AuthorizationV1(), username, "", updateRule.Verbs[0], "", schema.GroupResource{Group: updateRule.APIGroups[0], Resource: updateRule.Resources[0]}, true) + authutil.WaitForNamedAuthorizationUpdate(t, context.TODO(), client.AuthorizationV1(), username, "", approveRule.Verbs[0], approveRule.ResourceNames[0], schema.GroupResource{Group: approveRule.APIGroups[0], Resource: approveRule.Resources[0]}, true) + authutil.WaitForNamedAuthorizationUpdate(t, context.TODO(), client.AuthorizationV1(), username, "", updateRule.Verbs[0], "", schema.GroupResource{Group: updateRule.APIGroups[0], Resource: updateRule.Resources[0]}, true) } func buildApprovalClusterRoleForSigners(name string, signerNames ...string) *rbacv1.ClusterRole { diff --git a/test/integration/certificates/admission_sign_test.go b/test/integration/certificates/admission_sign_test.go index ffd803aa289..9e1ae99922b 100644 --- a/test/integration/certificates/admission_sign_test.go +++ b/test/integration/certificates/admission_sign_test.go @@ -26,8 +26,8 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" clientset "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" - kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" + "k8s.io/kubernetes/test/integration/authutil" "k8s.io/kubernetes/test/integration/framework" ) @@ -123,8 +123,8 @@ func grantUserPermissionToSignFor(t *testing.T, client clientset.Interface, user } signRule := cr.Rules[0] statusRule := cr.Rules[1] - waitForNamedAuthorizationUpdate(t, client.AuthorizationV1(), username, "", signRule.Verbs[0], signRule.ResourceNames[0], schema.GroupResource{Group: signRule.APIGroups[0], Resource: signRule.Resources[0]}, true) - waitForNamedAuthorizationUpdate(t, client.AuthorizationV1(), username, "", statusRule.Verbs[0], "", schema.GroupResource{Group: statusRule.APIGroups[0], Resource: statusRule.Resources[0]}, true) + authutil.WaitForNamedAuthorizationUpdate(t, context.TODO(), client.AuthorizationV1(), username, "", signRule.Verbs[0], signRule.ResourceNames[0], schema.GroupResource{Group: signRule.APIGroups[0], Resource: signRule.Resources[0]}, true) + authutil.WaitForNamedAuthorizationUpdate(t, context.TODO(), client.AuthorizationV1(), username, "", statusRule.Verbs[0], "", schema.GroupResource{Group: statusRule.APIGroups[0], Resource: statusRule.Resources[0]}, true) } func buildSigningClusterRoleForSigners(name string, signerNames ...string) *rbacv1.ClusterRole { diff --git a/test/integration/certificates/admission_test.go b/test/integration/certificates/admission_test.go deleted file mode 100644 index 34989a16ddf..00000000000 --- a/test/integration/certificates/admission_test.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package certificates - -import ( - "context" - "testing" - "time" - - authorizationv1 "k8s.io/api/authorization/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/wait" - v1authorization "k8s.io/client-go/kubernetes/typed/authorization/v1" -) - -// waitForNamedAuthorizationUpdate checks if the given user can perform the named verb and action on the named resource. -// Copied from k8s.io/kubernetes/test/e2e/framework/auth. -func waitForNamedAuthorizationUpdate(t *testing.T, c v1authorization.SubjectAccessReviewsGetter, user, namespace, verb, resourceName string, resource schema.GroupResource, allowed bool) { - review := &authorizationv1.SubjectAccessReview{ - Spec: authorizationv1.SubjectAccessReviewSpec{ - ResourceAttributes: &authorizationv1.ResourceAttributes{ - Group: resource.Group, - Verb: verb, - Resource: resource.Resource, - Namespace: namespace, - Name: resourceName, - }, - User: user, - }, - } - - if err := wait.Poll(time.Millisecond*100, time.Second*5, func() (bool, error) { - response, err := c.SubjectAccessReviews().Create(context.TODO(), review, metav1.CreateOptions{}) - if err != nil { - return false, err - } - if response.Status.Allowed != allowed { - return false, nil - } - return true, nil - }); err != nil { - t.Fatal(err) - } -} diff --git a/test/integration/certificates/controller_approval_test.go b/test/integration/certificates/controller_approval_test.go index 1abf147c489..380adffea4b 100644 --- a/test/integration/certificates/controller_approval_test.go +++ b/test/integration/certificates/controller_approval_test.go @@ -33,10 +33,10 @@ import ( "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" - kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" "k8s.io/kubernetes/pkg/controller/certificates" "k8s.io/kubernetes/pkg/controller/certificates/approver" + "k8s.io/kubernetes/test/integration/authutil" "k8s.io/kubernetes/test/integration/framework" ) @@ -196,7 +196,7 @@ func grantUserNodeClientPermissions(t *testing.T, client clientset.Interface, us t.Fatalf("failed to create test fixtures: %v", err) } rule := cr.Rules[0] - waitForNamedAuthorizationUpdate(t, client.AuthorizationV1(), username, "", rule.Verbs[0], "", schema.GroupResource{Group: rule.APIGroups[0], Resource: rule.Resources[0]}, true) + authutil.WaitForNamedAuthorizationUpdate(t, context.TODO(), client.AuthorizationV1(), username, "", rule.Verbs[0], "", schema.GroupResource{Group: rule.APIGroups[0], Resource: rule.Resources[0]}, true) } func buildNodeClientRoleForUser(name string, resourceType string) *rbacv1.ClusterRole {