From 34fd0ea2e2b94e4070212ef7925e944e904b8e47 Mon Sep 17 00:00:00 2001 From: Monis Khan Date: Tue, 10 Aug 2021 00:03:33 -0400 Subject: [PATCH] impersonation proxy: assert nested UID impersonation is disallowed Signed-off-by: Monis Khan --- .../concierge_impersonation_proxy_test.go | 82 ++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/test/integration/concierge_impersonation_proxy_test.go b/test/integration/concierge_impersonation_proxy_test.go index 66c53e13..21e90f69 100644 --- a/test/integration/concierge_impersonation_proxy_test.go +++ b/test/integration/concierge_impersonation_proxy_test.go @@ -751,6 +751,79 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl }, err) }) + t.Run("nested impersonation as a cluster admin fails if UID impersonation is attempted", func(t *testing.T) { + parallelIfNotEKS(t) + adminClientRestConfig := testlib.NewClientConfig(t) + clusterAdminCredentials := getCredForConfig(t, adminClientRestConfig) + + nestedImpersonationUIDOnly := newImpersonationProxyConfigWithCredentials(t, + clusterAdminCredentials, impersonationProxyURL, impersonationProxyCACertPEM, nil, + ) + nestedImpersonationUIDOnly.Wrap(func(rt http.RoundTripper) http.RoundTripper { + return roundtripper.Func(func(r *http.Request) (*http.Response, error) { + r.Header.Set("iMperSONATE-uid", "some-awesome-uid") + return rt.RoundTrip(r) + }) + }) + + _, errUID := testlib.NewKubeclient(t, nestedImpersonationUIDOnly).Kubernetes.CoreV1().Secrets("foo").Get(ctx, "bar", metav1.GetOptions{}) + msg := `Internal Server Error: "/api/v1/namespaces/foo/secrets/bar": requested [{UID some-awesome-uid authentication.k8s.io/v1 }] without impersonating a user` + full := fmt.Sprintf(`an error on the server (%q) has prevented the request from succeeding (get secrets bar)`, msg) + require.EqualError(t, errUID, full) + require.True(t, k8serrors.IsInternalError(errUID), errUID) + require.Equal(t, &k8serrors.StatusError{ + ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusInternalServerError, + Reason: metav1.StatusReasonInternalError, + Details: &metav1.StatusDetails{ + Name: "bar", + Kind: "secrets", + Causes: []metav1.StatusCause{ + { + Type: metav1.CauseTypeUnexpectedServerResponse, + Message: msg, + }, + }, + }, + Message: full, + }, + }, errUID) + + nestedImpersonationUID := newImpersonationProxyConfigWithCredentials(t, + clusterAdminCredentials, impersonationProxyURL, impersonationProxyCACertPEM, + &rest.ImpersonationConfig{ + UserName: "other-user-to-impersonate", + Groups: []string{"system:masters"}, // impersonate system:masters so we get past authorization checks + }, + ) + nestedImpersonationUID.Wrap(func(rt http.RoundTripper) http.RoundTripper { + return roundtripper.Func(func(r *http.Request) (*http.Response, error) { + r.Header.Set("imperSONate-uiD", "some-fancy-uid") + return rt.RoundTrip(r) + }) + }) + + _, err := testlib.NewKubeclient(t, nestedImpersonationUID).Kubernetes.CoreV1().Secrets(env.ConciergeNamespace).Get(ctx, impersonationProxyTLSSecretName(env), metav1.GetOptions{}) + require.EqualError(t, err, "Internal error occurred: unimplemented functionality - unable to act as current user") + require.True(t, k8serrors.IsInternalError(err), err) + require.Equal(t, &k8serrors.StatusError{ + ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusInternalServerError, + Reason: metav1.StatusReasonInternalError, + Details: &metav1.StatusDetails{ + Causes: []metav1.StatusCause{ + { + Message: "unimplemented functionality - unable to act as current user", + }, + }, + }, + Message: "Internal error occurred: unimplemented functionality - unable to act as current user", + }, + }, err) + }) + // this works because impersonation cannot set UID and thus the final user info the proxy sees has no UID t.Run("nested impersonation as a service account is allowed if it has enough RBAC permissions", func(t *testing.T) { parallelIfNotEKS(t) @@ -2168,6 +2241,13 @@ func createTokenCredentialRequest( func newImpersonationProxyClientWithCredentials(t *testing.T, credentials *loginv1alpha1.ClusterCredential, impersonationProxyURL string, impersonationProxyCACertPEM []byte, nestedImpersonationConfig *rest.ImpersonationConfig) *kubeclient.Client { t.Helper() + kubeconfig := newImpersonationProxyConfigWithCredentials(t, credentials, impersonationProxyURL, impersonationProxyCACertPEM, nestedImpersonationConfig) + return testlib.NewKubeclient(t, kubeconfig) +} + +func newImpersonationProxyConfigWithCredentials(t *testing.T, credentials *loginv1alpha1.ClusterCredential, impersonationProxyURL string, impersonationProxyCACertPEM []byte, nestedImpersonationConfig *rest.ImpersonationConfig) *rest.Config { + t.Helper() + env := testlib.IntegrationEnv(t) clusterSupportsLoadBalancers := env.HasCapability(testlib.HasExternalLoadBalancerProvider) @@ -2177,7 +2257,7 @@ func newImpersonationProxyClientWithCredentials(t *testing.T, credentials *login // Prefer to go through a load balancer because that's how the impersonator is intended to be used in the real world. kubeconfig.Proxy = kubeconfigProxyFunc(t, env.Proxy) } - return testlib.NewKubeclient(t, kubeconfig) + return kubeconfig } func newAnonymousImpersonationProxyClient(t *testing.T, impersonationProxyURL string, impersonationProxyCACertPEM []byte, nestedImpersonationConfig *rest.ImpersonationConfig) *kubeclient.Client {