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.
This commit is contained in:
Margo Crawford 2021-03-04 15:19:52 -08:00
parent 60a714058b
commit 74f5ed6b17
9 changed files with 423 additions and 70 deletions

View File

@ -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

View File

@ -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)
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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,
)
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -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 {