Fix bug which prevented watches from working through impersonator

Also:
- Changed base64 encoding of impersonator bearer tokens to use
  `base64.StdEncoding` to make it easier for users to manually
  create a token using the unix `base64` command
- Test the headers which are and are not passed through to the Kube API
  by the impersonator more carefully in the unit tests
- More WIP on concierge_impersonation_proxy_test.go

Signed-off-by: Margo Crawford <margaretc@vmware.com>
This commit is contained in:
Ryan Richard
2021-02-22 17:23:11 -08:00
committed by Margo Crawford
parent b8592a361c
commit 80ff5c1f17
9 changed files with 250 additions and 88 deletions
@@ -12,8 +12,12 @@ import (
"time"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/authorization/v1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
k8sinformers "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"sigs.k8s.io/yaml"
@@ -124,12 +128,94 @@ func TestImpersonationProxy(t *testing.T) {
}
t.Run("watching all the verbs", func(t *testing.T) {
// Start a watch in a informer.
// Create an RBAC rule to allow this user to read/write everything.
// t.Cleanup Delete the RBAC rule.
// Create a namespace, because it will be easier to deletecollection if we have a namespace.
// t.Cleanup Delete the namespace.
// Then "create" several Secrets.
namespace, err := adminClient.CoreV1().Namespaces().Create(ctx, &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{GenerateName: "impersonation-integration-test-"},
}, metav1.CreateOptions{})
require.NoError(t, err)
t.Cleanup(func() {
t.Logf("cleaning up test namespace %s", namespace.Name)
err = adminClient.CoreV1().Namespaces().Delete(context.Background(), namespace.Name, metav1.DeleteOptions{})
require.NoError(t, err)
})
// Create an RBAC rule to allow this user to read/write everything.
library.CreateTestClusterRoleBinding(
t,
rbacv1.Subject{
Kind: rbacv1.UserKind,
APIGroup: rbacv1.GroupName,
Name: env.TestUser.ExpectedUsername,
},
rbacv1.RoleRef{
Kind: "ClusterRole",
APIGroup: rbacv1.GroupName,
Name: "cluster-admin",
},
)
library.WaitForUserToHaveAccess(t, env.TestUser.ExpectedUsername, []string{}, &v1.ResourceAttributes{
Namespace: namespace.Name,
Verb: "create",
Group: "",
Version: "v1",
Resource: "configmaps",
})
// Create and start informer.
informerFactory := k8sinformers.NewSharedInformerFactoryWithOptions(
impersonationProxyClient,
0,
k8sinformers.WithNamespace(namespace.Name))
informer := informerFactory.Core().V1().ConfigMaps()
informer.Informer() // makes sure that the informer will cache
stopChannel := make(chan struct{})
informerFactory.Start(stopChannel)
t.Cleanup(func() {
stopChannel <- struct{}{}
})
informerFactory.WaitForCacheSync(ctx.Done())
// Test "create" verb.
_, err = impersonationProxyClient.CoreV1().ConfigMaps(namespace.Name).Create(
ctx,
&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "configmap-1"}},
metav1.CreateOptions{},
)
require.NoError(t, err)
require.Eventually(t, func() bool {
_, err = informer.Lister().ConfigMaps(namespace.Name).Get("configmap-1")
return err == nil
}, 10*time.Second, 500*time.Millisecond)
_, err = impersonationProxyClient.CoreV1().ConfigMaps(namespace.Name).Create(
ctx,
&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "configmap-2"}},
metav1.CreateOptions{},
)
require.NoError(t, err)
require.Eventually(t, func() bool {
_, err = informer.Lister().ConfigMaps(namespace.Name).Get("configmap-2")
return err == nil
}, 10*time.Second, 500*time.Millisecond)
_, err = impersonationProxyClient.CoreV1().ConfigMaps(namespace.Name).Create(
ctx,
&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "configmap-3"}},
metav1.CreateOptions{},
)
require.NoError(t, err)
require.Eventually(t, func() bool {
_, err = informer.Lister().ConfigMaps(namespace.Name).Get("configmap-3")
return err == nil
}, 10*time.Second, 500*time.Millisecond)
require.Eventually(t, func() bool {
configmaps, err := informer.Lister().ConfigMaps(namespace.Name).List(labels.Everything())
return err == nil && len(configmaps) == 3
}, 10*time.Second, 500*time.Millisecond)
// TODO, test more verbs
// "get" one them.
// "list" them all.
// "update" one of them.
@@ -137,7 +223,6 @@ func TestImpersonationProxy(t *testing.T) {
// "delete" one of them.
// "deletecollection" all of them.
// Make sure the watch sees all of those actions.
// Close the informer.
})
// Update configuration to force the proxy to disabled mode
+7
View File
@@ -23,6 +23,7 @@ import (
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
"github.com/stretchr/testify/require"
authorizationv1 "k8s.io/api/authorization/v1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
@@ -135,6 +136,12 @@ func TestE2EFullIntegration(t *testing.T) {
rbacv1.Subject{Kind: rbacv1.UserKind, APIGroup: rbacv1.GroupName, Name: env.SupervisorTestUpstream.Username},
rbacv1.RoleRef{Kind: "ClusterRole", APIGroup: rbacv1.GroupName, Name: "view"},
)
library.WaitForUserToHaveAccess(t, env.SupervisorTestUpstream.Username, []string{}, &authorizationv1.ResourceAttributes{
Verb: "get",
Group: "",
Version: "v1",
Resource: "namespaces",
})
// Use a specific session cache for this test.
sessionCachePath := tempDir + "/sessions.yaml"
+14 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package library
@@ -13,6 +13,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
authorizationv1 "k8s.io/api/authorization/v1"
v1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -137,6 +138,12 @@ func addTestClusterUserCanViewEverythingRoleBinding(t *testing.T, testUsername s
Name: "view",
},
)
WaitForUserToHaveAccess(t, testUsername, []string{}, &authorizationv1.ResourceAttributes{
Verb: "get",
Group: "",
Version: "v1",
Resource: "namespaces",
})
}
func addTestClusterGroupCanViewEverythingRoleBinding(t *testing.T, testGroup string) {
@@ -154,6 +161,12 @@ func addTestClusterGroupCanViewEverythingRoleBinding(t *testing.T, testGroup str
Name: "view",
},
)
WaitForUserToHaveAccess(t, "", []string{testGroup}, &authorizationv1.ResourceAttributes{
Verb: "get",
Group: "",
Version: "v1",
Resource: "namespaces",
})
}
func runKubectlGetNamespaces(t *testing.T, kubeConfigYAML string) (string, error) {
+22
View File
@@ -17,6 +17,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
authorizationv1 "k8s.io/api/authorization/v1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
@@ -434,6 +435,27 @@ func CreateTestClusterRoleBinding(t *testing.T, subject rbacv1.Subject, roleRef
return created
}
func WaitForUserToHaveAccess(t *testing.T, user string, groups []string, shouldHaveAccessTo *authorizationv1.ResourceAttributes) {
t.Helper()
client := NewKubernetesClientset(t)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
RequireEventuallyWithoutError(t, func() (bool, error) {
subjectAccessReview, err := client.AuthorizationV1().SubjectAccessReviews().Create(ctx,
&authorizationv1.SubjectAccessReview{
Spec: authorizationv1.SubjectAccessReviewSpec{
ResourceAttributes: shouldHaveAccessTo,
User: user,
Groups: groups,
}}, metav1.CreateOptions{})
if err != nil {
return false, err
}
return subjectAccessReview.Status.Allowed, nil
}, 10*time.Second, 500*time.Millisecond)
}
func testObjectMeta(t *testing.T, baseName string) metav1.ObjectMeta {
return metav1.ObjectMeta{
GenerateName: fmt.Sprintf("test-%s-", baseName),