mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 19:01:49 +00:00
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:
parent
60a714058b
commit
74f5ed6b17
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
121
test/integration/authutil/authutil.go
Normal file
121
test/integration/authutil/authutil.go
Normal 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,
|
||||
)
|
||||
}
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user