Add integration test for using WhoAmIRequest through impersonator
This commit is contained in:
parent
24396b6af1
commit
d13bb07b3e
4
go.mod
4
go.mod
@ -15,7 +15,7 @@ require (
|
|||||||
github.com/google/go-cmp v0.5.5
|
github.com/google/go-cmp v0.5.5
|
||||||
github.com/google/gofuzz v1.2.0
|
github.com/google/gofuzz v1.2.0
|
||||||
github.com/gorilla/securecookie v1.1.1
|
github.com/gorilla/securecookie v1.1.1
|
||||||
github.com/gorilla/websocket v1.4.2 // indirect
|
github.com/gorilla/websocket v1.4.2
|
||||||
github.com/oleiade/reflections v1.0.1 // indirect
|
github.com/oleiade/reflections v1.0.1 // indirect
|
||||||
github.com/onsi/ginkgo v1.13.0 // indirect
|
github.com/onsi/ginkgo v1.13.0 // indirect
|
||||||
github.com/ory/fosite v0.38.0
|
github.com/ory/fosite v0.38.0
|
||||||
@ -27,7 +27,7 @@ require (
|
|||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620
|
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
||||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d // indirect
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d // indirect
|
||||||
|
@ -22,7 +22,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
v1 "k8s.io/api/authorization/v1"
|
v1 "k8s.io/api/authorization/v1"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
@ -38,9 +37,11 @@ import (
|
|||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
"go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
|
"go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
|
||||||
|
identityv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/identity/v1alpha1"
|
||||||
loginv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/login/v1alpha1"
|
loginv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/login/v1alpha1"
|
||||||
"go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
|
"go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
|
||||||
"go.pinniped.dev/internal/concierge/impersonator"
|
"go.pinniped.dev/internal/concierge/impersonator"
|
||||||
|
"go.pinniped.dev/internal/kubeclient"
|
||||||
"go.pinniped.dev/internal/testutil"
|
"go.pinniped.dev/internal/testutil"
|
||||||
"go.pinniped.dev/test/library"
|
"go.pinniped.dev/test/library"
|
||||||
)
|
)
|
||||||
@ -59,35 +60,20 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
adminClient := library.NewKubernetesClientset(t)
|
adminClient := library.NewKubernetesClientset(t)
|
||||||
adminConciergeClient := library.NewConciergeClientset(t)
|
adminConciergeClient := library.NewConciergeClientset(t)
|
||||||
|
|
||||||
// Create a WebhookAuthenticator.
|
// Create a WebhookAuthenticator and prepare a TokenCredentialRequestSpec using the authenticator for use later.
|
||||||
authenticator := library.CreateTestWebhookAuthenticator(ctx, t)
|
credentialRequestSpecWithWorkingCredentials := loginv1alpha1.TokenCredentialRequestSpec{
|
||||||
|
Token: env.TestUser.Token,
|
||||||
|
Authenticator: library.CreateTestWebhookAuthenticator(ctx, t),
|
||||||
|
}
|
||||||
|
|
||||||
// The address of the ClusterIP service that points at the impersonation proxy's port (used when there is no load balancer).
|
// The address of the ClusterIP service that points at the impersonation proxy's port (used when there is no load balancer).
|
||||||
proxyServiceEndpoint := fmt.Sprintf("%s-proxy.%s.svc.cluster.local", env.ConciergeAppName, env.ConciergeNamespace)
|
proxyServiceEndpoint := fmt.Sprintf("%s-proxy.%s.svc.cluster.local", env.ConciergeAppName, env.ConciergeNamespace)
|
||||||
// The error message that will be returned by squid when the impersonation proxy port inside the cluster is not listening.
|
// The error message that will be returned by squid when the impersonation proxy port inside the cluster is not listening.
|
||||||
serviceUnavailableViaSquidError := fmt.Sprintf(`Get "https://%s/api/v1/namespaces": Service Unavailable`, proxyServiceEndpoint)
|
serviceUnavailableViaSquidError := fmt.Sprintf(`Get "https://%s/api/v1/namespaces": Service Unavailable`, proxyServiceEndpoint)
|
||||||
|
|
||||||
credentialRequestSpecWithWorkingCredentials := loginv1alpha1.TokenCredentialRequestSpec{
|
var mostRecentTokenCredentialRequestResponse *loginv1alpha1.TokenCredentialRequest
|
||||||
Token: env.TestUser.Token,
|
|
||||||
Authenticator: authenticator,
|
|
||||||
}
|
|
||||||
|
|
||||||
credentialAlmostExpired := func(credential *loginv1alpha1.TokenCredentialRequest) bool {
|
|
||||||
pemBlock, _ := pem.Decode([]byte(credential.Status.Credential.ClientCertificateData))
|
|
||||||
parsedCredential, err := x509.ParseCertificate(pemBlock.Bytes)
|
|
||||||
require.NoError(t, err)
|
|
||||||
timeRemaining := time.Until(parsedCredential.NotAfter)
|
|
||||||
if timeRemaining < 2*time.Minute {
|
|
||||||
t.Logf("The TokenCredentialRequest cred is almost expired and needs to be refreshed. Expires in %s.", timeRemaining)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
t.Logf("The TokenCredentialRequest cred is good for some more time (%s) so using it.", timeRemaining)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var tokenCredentialRequestResponse *loginv1alpha1.TokenCredentialRequest
|
|
||||||
refreshCredential := func() *loginv1alpha1.ClusterCredential {
|
refreshCredential := func() *loginv1alpha1.ClusterCredential {
|
||||||
if tokenCredentialRequestResponse == nil || credentialAlmostExpired(tokenCredentialRequestResponse) {
|
if mostRecentTokenCredentialRequestResponse == nil || credentialAlmostExpired(t, mostRecentTokenCredentialRequestResponse) {
|
||||||
var err error
|
var err error
|
||||||
// Make a TokenCredentialRequest. This can either return a cert signed by the Kube API server's CA (e.g. on kind)
|
// Make a TokenCredentialRequest. This can either return a cert signed by the Kube API server's CA (e.g. on kind)
|
||||||
// or a cert signed by the impersonator's signing CA (e.g. on GKE). Either should be accepted by the impersonation
|
// or a cert signed by the impersonator's signing CA (e.g. on GKE). Either should be accepted by the impersonation
|
||||||
@ -95,80 +81,46 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
//
|
//
|
||||||
// However, we issue short-lived certs, so this cert will only be valid for a few minutes.
|
// However, we issue short-lived certs, so this cert will only be valid for a few minutes.
|
||||||
// Cache it until it is almost expired and then refresh it whenever it is close to expired.
|
// Cache it until it is almost expired and then refresh it whenever it is close to expired.
|
||||||
tokenCredentialRequestResponse, err = library.CreateTokenCredentialRequest(ctx, t, credentialRequestSpecWithWorkingCredentials)
|
mostRecentTokenCredentialRequestResponse, err = library.CreateTokenCredentialRequest(ctx, t, credentialRequestSpecWithWorkingCredentials)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Nil(t, tokenCredentialRequestResponse.Status.Message,
|
require.Nil(t, mostRecentTokenCredentialRequestResponse.Status.Message,
|
||||||
"expected no error message but got: %s", library.Sdump(tokenCredentialRequestResponse.Status.Message))
|
"expected no error message but got: %s", library.Sdump(mostRecentTokenCredentialRequestResponse.Status.Message))
|
||||||
require.NotEmpty(t, tokenCredentialRequestResponse.Status.Credential.ClientCertificateData)
|
require.NotEmpty(t, mostRecentTokenCredentialRequestResponse.Status.Credential.ClientCertificateData)
|
||||||
require.NotEmpty(t, tokenCredentialRequestResponse.Status.Credential.ClientKeyData)
|
require.NotEmpty(t, mostRecentTokenCredentialRequestResponse.Status.Credential.ClientKeyData)
|
||||||
|
|
||||||
// At the moment the credential request should not have returned a token. In the future, if we make it return
|
// At the moment the credential request should not have returned a token. In the future, if we make it return
|
||||||
// tokens, we should revisit this test's rest config below.
|
// tokens, we should revisit this test's rest config below.
|
||||||
require.Empty(t, tokenCredentialRequestResponse.Status.Credential.Token)
|
require.Empty(t, mostRecentTokenCredentialRequestResponse.Status.Credential.Token)
|
||||||
}
|
}
|
||||||
return tokenCredentialRequestResponse.Status.Credential
|
return mostRecentTokenCredentialRequestResponse.Status.Credential
|
||||||
}
|
}
|
||||||
|
|
||||||
impersonationProxyRestConfig := func(credential *loginv1alpha1.ClusterCredential, host string, caData []byte, doubleImpersonateUser string) *rest.Config {
|
impersonationProxyViaSquidKubeClientWithoutCredential := func() kubernetes.Interface {
|
||||||
config := rest.Config{
|
|
||||||
Host: host,
|
|
||||||
TLSClientConfig: rest.TLSClientConfig{
|
|
||||||
Insecure: caData == nil,
|
|
||||||
CAData: caData,
|
|
||||||
CertData: []byte(credential.ClientCertificateData),
|
|
||||||
KeyData: []byte(credential.ClientKeyData),
|
|
||||||
},
|
|
||||||
// kubectl would set both the client cert and the token, so we'll do that too.
|
|
||||||
// The Kube API server will ignore the token if the client cert successfully authenticates.
|
|
||||||
// Only if the client cert is not present or fails to authenticate will it use the token.
|
|
||||||
// Historically, it works that way because some web browsers will always send your
|
|
||||||
// corporate-assigned client cert even if it is not valid, and it doesn't want to treat
|
|
||||||
// that as a failure if you also sent a perfectly good token.
|
|
||||||
// We would like the impersonation proxy to imitate that behavior, so we test it here.
|
|
||||||
BearerToken: "this is not valid",
|
|
||||||
}
|
|
||||||
if doubleImpersonateUser != "" {
|
|
||||||
config.Impersonate = rest.ImpersonationConfig{UserName: doubleImpersonateUser}
|
|
||||||
}
|
|
||||||
return &config
|
|
||||||
}
|
|
||||||
|
|
||||||
kubeconfigProxyFunc := func() func(req *http.Request) (*url.URL, error) {
|
|
||||||
return func(req *http.Request) (*url.URL, error) {
|
|
||||||
proxyURL, err := url.Parse(env.Proxy)
|
|
||||||
require.NoError(t, err)
|
|
||||||
t.Logf("passing request for %s through proxy %s", req.URL, proxyURL.String())
|
|
||||||
return proxyURL, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impersonationProxyViaSquidClient := func(proxyURL string, caData []byte, doubleImpersonateUser string) kubernetes.Interface {
|
|
||||||
t.Helper()
|
|
||||||
kubeconfig := impersonationProxyRestConfig(refreshCredential(), proxyURL, caData, doubleImpersonateUser)
|
|
||||||
kubeconfig.Proxy = kubeconfigProxyFunc()
|
|
||||||
return library.NewKubeclient(t, kubeconfig).Kubernetes
|
|
||||||
}
|
|
||||||
|
|
||||||
impersonationProxyViaSquidClientWithoutCredential := func() kubernetes.Interface {
|
|
||||||
t.Helper()
|
|
||||||
proxyURL := "https://" + proxyServiceEndpoint
|
proxyURL := "https://" + proxyServiceEndpoint
|
||||||
kubeconfig := impersonationProxyRestConfig(&loginv1alpha1.ClusterCredential{}, proxyURL, nil, "")
|
kubeconfig := impersonationProxyRestConfig(&loginv1alpha1.ClusterCredential{}, proxyURL, nil, "")
|
||||||
kubeconfig.Proxy = kubeconfigProxyFunc()
|
kubeconfig.Proxy = kubeconfigProxyFunc(t, env.Proxy)
|
||||||
return library.NewKubeclient(t, kubeconfig).Kubernetes
|
return library.NewKubeclient(t, kubeconfig).Kubernetes
|
||||||
}
|
}
|
||||||
|
|
||||||
impersonationProxyViaLoadBalancerClient := func(proxyURL string, caData []byte, doubleImpersonateUser string) kubernetes.Interface {
|
newImpersonationProxyClientWithCredentials := func(credentials *loginv1alpha1.ClusterCredential, impersonationProxyURL string, impersonationProxyCACertPEM []byte, doubleImpersonateUser string) *kubeclient.Client {
|
||||||
t.Helper()
|
kubeconfig := impersonationProxyRestConfig(credentials, impersonationProxyURL, impersonationProxyCACertPEM, doubleImpersonateUser)
|
||||||
kubeconfig := impersonationProxyRestConfig(refreshCredential(), proxyURL, caData, doubleImpersonateUser)
|
if !env.HasCapability(library.HasExternalLoadBalancerProvider) {
|
||||||
return library.NewKubeclient(t, kubeconfig).Kubernetes
|
// Send traffic through the Squid proxy
|
||||||
|
kubeconfig.Proxy = kubeconfigProxyFunc(t, env.Proxy)
|
||||||
|
}
|
||||||
|
return library.NewKubeclient(t, kubeconfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
newImpersonationProxyClient := func(proxyURL string, impersonationProxyCACertPEM []byte, doubleImpersonateUser string) kubernetes.Interface {
|
newImpersonationProxyClient := func(impersonationProxyURL string, impersonationProxyCACertPEM []byte, doubleImpersonateUser string) *kubeclient.Client {
|
||||||
if env.HasCapability(library.HasExternalLoadBalancerProvider) {
|
refreshedCredentials := refreshCredential()
|
||||||
return impersonationProxyViaLoadBalancerClient(proxyURL, impersonationProxyCACertPEM, doubleImpersonateUser)
|
refreshedCredentials.Token = "not a valid token" // demonstrates that client certs take precedence over tokens by setting both on the requests
|
||||||
|
return newImpersonationProxyClientWithCredentials(refreshedCredentials, impersonationProxyURL, impersonationProxyCACertPEM, doubleImpersonateUser)
|
||||||
}
|
}
|
||||||
return impersonationProxyViaSquidClient(proxyURL, impersonationProxyCACertPEM, doubleImpersonateUser)
|
|
||||||
|
newAnonymousImpersonationProxyClient := func(impersonationProxyURL string, impersonationProxyCACertPEM []byte, doubleImpersonateUser string) *kubeclient.Client {
|
||||||
|
emptyCredentials := &loginv1alpha1.ClusterCredential{}
|
||||||
|
return newImpersonationProxyClientWithCredentials(emptyCredentials, impersonationProxyURL, impersonationProxyCACertPEM, doubleImpersonateUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
oldConfigMap, err := adminClient.CoreV1().ConfigMaps(env.ConciergeNamespace).Get(ctx, impersonationProxyConfigMapName(env), metav1.GetOptions{})
|
oldConfigMap, err := adminClient.CoreV1().ConfigMaps(env.ConciergeNamespace).Get(ctx, impersonationProxyConfigMapName(env), metav1.GetOptions{})
|
||||||
@ -216,7 +168,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
}, 10*time.Second, 500*time.Millisecond)
|
}, 10*time.Second, 500*time.Millisecond)
|
||||||
|
|
||||||
// Check that we can't use the impersonation proxy to execute kubectl commands yet.
|
// Check that we can't use the impersonation proxy to execute kubectl commands yet.
|
||||||
_, err = impersonationProxyViaSquidClientWithoutCredential().CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
|
_, err = impersonationProxyViaSquidKubeClientWithoutCredential().CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
|
||||||
require.EqualError(t, err, serviceUnavailableViaSquidError)
|
require.EqualError(t, err, serviceUnavailableViaSquidError)
|
||||||
|
|
||||||
// Create configuration to make the impersonation proxy turn on with a hard coded endpoint (without a load balancer).
|
// Create configuration to make the impersonation proxy turn on with a hard coded endpoint (without a load balancer).
|
||||||
@ -244,21 +196,21 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
// credentials before they expire. Create a closure to capture the arguments to newImpersonationProxyClient
|
// credentials before they expire. Create a closure to capture the arguments to newImpersonationProxyClient
|
||||||
// so we don't have to keep repeating them.
|
// so we don't have to keep repeating them.
|
||||||
// This client performs TLS checks, so it also provides test coverage that the impersonation proxy server is generating TLS certs correctly.
|
// This client performs TLS checks, so it also provides test coverage that the impersonation proxy server is generating TLS certs correctly.
|
||||||
impersonationProxyClient := func() kubernetes.Interface {
|
impersonationProxyKubeClient := func() kubernetes.Interface {
|
||||||
return newImpersonationProxyClient(impersonationProxyURL, impersonationProxyCACertPEM, "")
|
return newImpersonationProxyClient(impersonationProxyURL, impersonationProxyCACertPEM, "").Kubernetes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that the user can perform basic actions through the client with their username and group membership
|
// Test that the user can perform basic actions through the client with their username and group membership
|
||||||
// influencing RBAC checks correctly.
|
// influencing RBAC checks correctly.
|
||||||
t.Run(
|
t.Run(
|
||||||
"access as user",
|
"access as user",
|
||||||
library.AccessAsUserTest(ctx, env.TestUser.ExpectedUsername, impersonationProxyClient()),
|
library.AccessAsUserTest(ctx, env.TestUser.ExpectedUsername, impersonationProxyKubeClient()),
|
||||||
)
|
)
|
||||||
for _, group := range env.TestUser.ExpectedGroups {
|
for _, group := range env.TestUser.ExpectedGroups {
|
||||||
group := group
|
group := group
|
||||||
t.Run(
|
t.Run(
|
||||||
"access as group "+group,
|
"access as group "+group,
|
||||||
library.AccessAsGroupTest(ctx, group, impersonationProxyClient()),
|
library.AccessAsGroupTest(ctx, group, impersonationProxyKubeClient()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,7 +240,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
|
|
||||||
// Create and start informer to exercise the "watch" verb for us.
|
// Create and start informer to exercise the "watch" verb for us.
|
||||||
informerFactory := k8sinformers.NewSharedInformerFactoryWithOptions(
|
informerFactory := k8sinformers.NewSharedInformerFactoryWithOptions(
|
||||||
impersonationProxyClient(),
|
impersonationProxyKubeClient(),
|
||||||
0,
|
0,
|
||||||
k8sinformers.WithNamespace(namespace.Name))
|
k8sinformers.WithNamespace(namespace.Name))
|
||||||
informer := informerFactory.Core().V1().ConfigMaps()
|
informer := informerFactory.Core().V1().ConfigMaps()
|
||||||
@ -308,17 +260,17 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test "create" verb through the impersonation proxy.
|
// Test "create" verb through the impersonation proxy.
|
||||||
_, err = impersonationProxyClient().CoreV1().ConfigMaps(namespace.Name).Create(ctx,
|
_, err = impersonationProxyKubeClient().CoreV1().ConfigMaps(namespace.Name).Create(ctx,
|
||||||
&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "configmap-1", Labels: configMapLabels}},
|
&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "configmap-1", Labels: configMapLabels}},
|
||||||
metav1.CreateOptions{},
|
metav1.CreateOptions{},
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_, err = impersonationProxyClient().CoreV1().ConfigMaps(namespace.Name).Create(ctx,
|
_, err = impersonationProxyKubeClient().CoreV1().ConfigMaps(namespace.Name).Create(ctx,
|
||||||
&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "configmap-2", Labels: configMapLabels}},
|
&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "configmap-2", Labels: configMapLabels}},
|
||||||
metav1.CreateOptions{},
|
metav1.CreateOptions{},
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_, err = impersonationProxyClient().CoreV1().ConfigMaps(namespace.Name).Create(ctx,
|
_, err = impersonationProxyKubeClient().CoreV1().ConfigMaps(namespace.Name).Create(ctx,
|
||||||
&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "configmap-3", Labels: configMapLabels}},
|
&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "configmap-3", Labels: configMapLabels}},
|
||||||
metav1.CreateOptions{},
|
metav1.CreateOptions{},
|
||||||
)
|
)
|
||||||
@ -334,11 +286,11 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
}, 10*time.Second, 50*time.Millisecond)
|
}, 10*time.Second, 50*time.Millisecond)
|
||||||
|
|
||||||
// Test "get" verb through the impersonation proxy.
|
// Test "get" verb through the impersonation proxy.
|
||||||
configMap3, err := impersonationProxyClient().CoreV1().ConfigMaps(namespace.Name).Get(ctx, "configmap-3", metav1.GetOptions{})
|
configMap3, err := impersonationProxyKubeClient().CoreV1().ConfigMaps(namespace.Name).Get(ctx, "configmap-3", metav1.GetOptions{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Test "list" verb through the impersonation proxy.
|
// Test "list" verb through the impersonation proxy.
|
||||||
listResult, err := impersonationProxyClient().CoreV1().ConfigMaps(namespace.Name).List(ctx, metav1.ListOptions{
|
listResult, err := impersonationProxyKubeClient().CoreV1().ConfigMaps(namespace.Name).List(ctx, metav1.ListOptions{
|
||||||
LabelSelector: configMapLabels.String(),
|
LabelSelector: configMapLabels.String(),
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -346,7 +298,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
|
|
||||||
// Test "update" verb through the impersonation proxy.
|
// Test "update" verb through the impersonation proxy.
|
||||||
configMap3.Data = map[string]string{"foo": "bar"}
|
configMap3.Data = map[string]string{"foo": "bar"}
|
||||||
updateResult, err := impersonationProxyClient().CoreV1().ConfigMaps(namespace.Name).Update(ctx, configMap3, metav1.UpdateOptions{})
|
updateResult, err := impersonationProxyKubeClient().CoreV1().ConfigMaps(namespace.Name).Update(ctx, configMap3, metav1.UpdateOptions{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "bar", updateResult.Data["foo"])
|
require.Equal(t, "bar", updateResult.Data["foo"])
|
||||||
|
|
||||||
@ -357,7 +309,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
}, 10*time.Second, 50*time.Millisecond)
|
}, 10*time.Second, 50*time.Millisecond)
|
||||||
|
|
||||||
// Test "patch" verb through the impersonation proxy.
|
// Test "patch" verb through the impersonation proxy.
|
||||||
patchResult, err := impersonationProxyClient().CoreV1().ConfigMaps(namespace.Name).Patch(ctx,
|
patchResult, err := impersonationProxyKubeClient().CoreV1().ConfigMaps(namespace.Name).Patch(ctx,
|
||||||
"configmap-3",
|
"configmap-3",
|
||||||
types.MergePatchType,
|
types.MergePatchType,
|
||||||
[]byte(`{"data":{"baz":"42"}}`),
|
[]byte(`{"data":{"baz":"42"}}`),
|
||||||
@ -374,7 +326,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
}, 10*time.Second, 50*time.Millisecond)
|
}, 10*time.Second, 50*time.Millisecond)
|
||||||
|
|
||||||
// Test "delete" verb through the impersonation proxy.
|
// Test "delete" verb through the impersonation proxy.
|
||||||
err = impersonationProxyClient().CoreV1().ConfigMaps(namespace.Name).Delete(ctx, "configmap-3", metav1.DeleteOptions{})
|
err = impersonationProxyKubeClient().CoreV1().ConfigMaps(namespace.Name).Delete(ctx, "configmap-3", metav1.DeleteOptions{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Make sure that the deleted ConfigMap shows up in the informer's cache.
|
// Make sure that the deleted ConfigMap shows up in the informer's cache.
|
||||||
@ -385,7 +337,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
}, 10*time.Second, 50*time.Millisecond)
|
}, 10*time.Second, 50*time.Millisecond)
|
||||||
|
|
||||||
// Test "deletecollection" verb through the impersonation proxy.
|
// Test "deletecollection" verb through the impersonation proxy.
|
||||||
err = impersonationProxyClient().CoreV1().ConfigMaps(namespace.Name).DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{})
|
err = impersonationProxyKubeClient().CoreV1().ConfigMaps(namespace.Name).DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Make sure that the deleted ConfigMaps shows up in the informer's cache.
|
// Make sure that the deleted ConfigMaps shows up in the informer's cache.
|
||||||
@ -395,7 +347,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
}, 10*time.Second, 50*time.Millisecond)
|
}, 10*time.Second, 50*time.Millisecond)
|
||||||
|
|
||||||
// There should be no ConfigMaps left.
|
// There should be no ConfigMaps left.
|
||||||
listResult, err = impersonationProxyClient().CoreV1().ConfigMaps(namespace.Name).List(ctx, metav1.ListOptions{
|
listResult, err = impersonationProxyKubeClient().CoreV1().ConfigMaps(namespace.Name).List(ctx, metav1.ListOptions{
|
||||||
LabelSelector: configMapLabels.String(),
|
LabelSelector: configMapLabels.String(),
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -415,16 +367,16 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
|
|
||||||
// Make a client which will send requests through the impersonation proxy and will also add
|
// Make a client which will send requests through the impersonation proxy and will also add
|
||||||
// impersonate headers to the request.
|
// impersonate headers to the request.
|
||||||
doubleImpersonationClient := newImpersonationProxyClient(impersonationProxyURL, impersonationProxyCACertPEM, "other-user-to-impersonate")
|
doubleImpersonationKubeClient := newImpersonationProxyClient(impersonationProxyURL, impersonationProxyCACertPEM, "other-user-to-impersonate").Kubernetes
|
||||||
|
|
||||||
// Check that we can get some resource through the impersonation proxy without any impersonation headers on the request.
|
// Check that we can get some resource through the impersonation proxy without any impersonation headers on the request.
|
||||||
// We could use any resource for this, but we happen to know that this one should exist.
|
// We could use any resource for this, but we happen to know that this one should exist.
|
||||||
_, err = impersonationProxyClient().CoreV1().Secrets(env.ConciergeNamespace).Get(ctx, impersonationProxyTLSSecretName(env), metav1.GetOptions{})
|
_, err = impersonationProxyKubeClient().CoreV1().Secrets(env.ConciergeNamespace).Get(ctx, impersonationProxyTLSSecretName(env), metav1.GetOptions{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Now we'll see what happens when we add an impersonation header to the request. This should generate a
|
// Now we'll see what happens when we add an impersonation header to the request. This should generate a
|
||||||
// request similar to the one above, except that it will have an impersonation header.
|
// request similar to the one above, except that it will also have an impersonation header.
|
||||||
_, err = doubleImpersonationClient.CoreV1().Secrets(env.ConciergeNamespace).Get(ctx, impersonationProxyTLSSecretName(env), metav1.GetOptions{})
|
_, err = doubleImpersonationKubeClient.CoreV1().Secrets(env.ConciergeNamespace).Get(ctx, impersonationProxyTLSSecretName(env), metav1.GetOptions{})
|
||||||
// Double impersonation is not supported yet, so we should get an error.
|
// Double impersonation is not supported yet, so we should get an error.
|
||||||
require.EqualError(t, err, fmt.Sprintf(
|
require.EqualError(t, err, fmt.Sprintf(
|
||||||
`users "other-user-to-impersonate" is forbidden: `+
|
`users "other-user-to-impersonate" is forbidden: `+
|
||||||
@ -433,6 +385,36 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
env.TestUser.ExpectedUsername))
|
env.TestUser.ExpectedUsername))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("using service account tokens to authenticate to impersonation proxy", func(t *testing.T) {
|
||||||
|
// TODO: test that this is not currently allowed
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("WhoAmIRequests through the impersonation proxy", func(t *testing.T) {
|
||||||
|
// Test using the TokenCredentialRequest for authentication.
|
||||||
|
impersonationProxyPinnipedConciergeClient := newImpersonationProxyClient(
|
||||||
|
impersonationProxyURL, impersonationProxyCACertPEM, "",
|
||||||
|
).PinnipedConcierge
|
||||||
|
whoAmI, err := impersonationProxyPinnipedConciergeClient.IdentityV1alpha1().WhoAmIRequests().
|
||||||
|
Create(ctx, &identityv1alpha1.WhoAmIRequest{}, metav1.CreateOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t,
|
||||||
|
expectedWhoAmIRequestResponse(env.TestUser.ExpectedUsername, append(env.TestUser.ExpectedGroups, "system:authenticated")),
|
||||||
|
whoAmI,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test an unauthenticated request which does not include any credentials.
|
||||||
|
impersonationProxyAnonymousPinnipedConciergeClient := newAnonymousImpersonationProxyClient(
|
||||||
|
impersonationProxyURL, impersonationProxyCACertPEM, "",
|
||||||
|
).PinnipedConcierge
|
||||||
|
whoAmI, err = impersonationProxyAnonymousPinnipedConciergeClient.IdentityV1alpha1().WhoAmIRequests().
|
||||||
|
Create(ctx, &identityv1alpha1.WhoAmIRequest{}, metav1.CreateOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t,
|
||||||
|
expectedWhoAmIRequestResponse("system:anonymous", []string{"system:unauthenticated"}),
|
||||||
|
whoAmI,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("kubectl as a client", func(t *testing.T) {
|
t.Run("kubectl as a client", func(t *testing.T) {
|
||||||
// Create an RBAC rule to allow this user to read/write everything.
|
// Create an RBAC rule to allow this user to read/write everything.
|
||||||
library.CreateTestClusterRoleBinding(t,
|
library.CreateTestClusterRoleBinding(t,
|
||||||
@ -526,7 +508,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
// Try "kubectl exec" through the impersonation proxy.
|
// Try "kubectl exec" through the impersonation proxy.
|
||||||
echoString := "hello world"
|
echoString := "hello world"
|
||||||
stdout, _, err := runKubectl("exec", "--namespace", env.ConciergeNamespace, podName, "--", "echo", echoString)
|
stdout, _, err := runKubectl("exec", "--namespace", env.ConciergeNamespace, podName, "--", "echo", echoString)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err, `"kubectl exec" failed`)
|
||||||
require.Equal(t, echoString+"\n", stdout)
|
require.Equal(t, echoString+"\n", stdout)
|
||||||
|
|
||||||
// run the kubectl port-forward command
|
// run the kubectl port-forward command
|
||||||
@ -537,7 +519,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
|
|
||||||
// start, but don't wait for the command to finish
|
// start, but don't wait for the command to finish
|
||||||
err = portForwardCmd.Start()
|
err = portForwardCmd.Start()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err, `"kubectl port-forward" failed`)
|
||||||
|
|
||||||
// then run curl something against it
|
// then run curl something against it
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
@ -589,7 +571,9 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
}
|
}
|
||||||
c, r, err := dialer.Dial(dest.String(), nil)
|
c, r, err := dialer.Dial(dest.String(), nil)
|
||||||
if r != nil {
|
if r != nil {
|
||||||
defer r.Body.Close()
|
defer func() {
|
||||||
|
require.NoError(t, r.Body.Close())
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
if err != nil && r != nil {
|
if err != nil && r != nil {
|
||||||
body, _ := ioutil.ReadAll(r.Body)
|
body, _ := ioutil.ReadAll(r.Body)
|
||||||
@ -656,7 +640,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
require.Eventually(t, func() bool {
|
require.Eventually(t, func() bool {
|
||||||
// It's okay if this returns RBAC errors because this user has no role bindings.
|
// It's okay if this returns RBAC errors because this user has no role bindings.
|
||||||
// What we want to see is that the proxy eventually shuts down entirely.
|
// What we want to see is that the proxy eventually shuts down entirely.
|
||||||
_, err = impersonationProxyViaSquidClientWithoutCredential().CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
|
_, err = impersonationProxyViaSquidKubeClientWithoutCredential().CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
|
||||||
return err.Error() == serviceUnavailableViaSquidError
|
return err.Error() == serviceUnavailableViaSquidError
|
||||||
}, 20*time.Second, 500*time.Millisecond)
|
}, 20*time.Second, 500*time.Millisecond)
|
||||||
}
|
}
|
||||||
@ -683,7 +667,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
// impersonation strategy, we should be left with no working strategies.
|
// impersonation strategy, we should be left with no working strategies.
|
||||||
// Given that there are no working strategies, a TokenCredentialRequest which would otherwise work should now
|
// Given that there are no working strategies, a TokenCredentialRequest which would otherwise work should now
|
||||||
// fail, because there is no point handing out credentials that are not going to work for any strategy.
|
// fail, because there is no point handing out credentials that are not going to work for any strategy.
|
||||||
tokenCredentialRequestResponse, err = library.CreateTokenCredentialRequest(ctx, t, credentialRequestSpecWithWorkingCredentials)
|
tokenCredentialRequestResponse, err := library.CreateTokenCredentialRequest(ctx, t, credentialRequestSpecWithWorkingCredentials)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.NotNil(t, tokenCredentialRequestResponse.Status.Message, "expected an error message but got nil")
|
require.NotNil(t, tokenCredentialRequestResponse.Status.Message, "expected an error message but got nil")
|
||||||
@ -693,6 +677,21 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func expectedWhoAmIRequestResponse(username string, groups []string) *identityv1alpha1.WhoAmIRequest {
|
||||||
|
return &identityv1alpha1.WhoAmIRequest{
|
||||||
|
Status: identityv1alpha1.WhoAmIRequestStatus{
|
||||||
|
KubernetesUserInfo: identityv1alpha1.KubernetesUserInfo{
|
||||||
|
User: identityv1alpha1.UserInfo{
|
||||||
|
Username: username,
|
||||||
|
UID: "", // no way to impersonate UID: https://github.com/kubernetes/kubernetes/issues/93699
|
||||||
|
Groups: groups,
|
||||||
|
Extra: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func performImpersonatorDiscovery(ctx context.Context, t *testing.T, env *library.TestEnv, adminConciergeClient versioned.Interface) (string, []byte) {
|
func performImpersonatorDiscovery(ctx context.Context, t *testing.T, env *library.TestEnv, adminConciergeClient versioned.Interface) (string, []byte) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
var impersonationProxyURL string
|
var impersonationProxyURL string
|
||||||
@ -767,6 +766,54 @@ func requireDisabledByConfigurationStrategy(ctx context.Context, t *testing.T, e
|
|||||||
}, 1*time.Minute, 500*time.Millisecond)
|
}, 1*time.Minute, 500*time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func credentialAlmostExpired(t *testing.T, credential *loginv1alpha1.TokenCredentialRequest) bool {
|
||||||
|
t.Helper()
|
||||||
|
pemBlock, _ := pem.Decode([]byte(credential.Status.Credential.ClientCertificateData))
|
||||||
|
parsedCredential, err := x509.ParseCertificate(pemBlock.Bytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
timeRemaining := time.Until(parsedCredential.NotAfter)
|
||||||
|
if timeRemaining < 2*time.Minute {
|
||||||
|
t.Logf("The TokenCredentialRequest cred is almost expired and needs to be refreshed. Expires in %s.", timeRemaining)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
t.Logf("The TokenCredentialRequest cred is good for some more time (%s) so using it.", timeRemaining)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func impersonationProxyRestConfig(credential *loginv1alpha1.ClusterCredential, host string, caData []byte, doubleImpersonateUser string) *rest.Config {
|
||||||
|
config := rest.Config{
|
||||||
|
Host: host,
|
||||||
|
TLSClientConfig: rest.TLSClientConfig{
|
||||||
|
Insecure: caData == nil,
|
||||||
|
CAData: caData,
|
||||||
|
CertData: []byte(credential.ClientCertificateData),
|
||||||
|
KeyData: []byte(credential.ClientKeyData),
|
||||||
|
},
|
||||||
|
// kubectl would set both the client cert and the token, so we'll do that too.
|
||||||
|
// The Kube API server will ignore the token if the client cert successfully authenticates.
|
||||||
|
// Only if the client cert is not present or fails to authenticate will it use the token.
|
||||||
|
// Historically, it works that way because some web browsers will always send your
|
||||||
|
// corporate-assigned client cert even if it is not valid, and it doesn't want to treat
|
||||||
|
// that as a failure if you also sent a perfectly good token.
|
||||||
|
// We would like the impersonation proxy to imitate that behavior, so we test it here.
|
||||||
|
BearerToken: credential.Token,
|
||||||
|
}
|
||||||
|
if doubleImpersonateUser != "" {
|
||||||
|
config.Impersonate = rest.ImpersonationConfig{UserName: doubleImpersonateUser}
|
||||||
|
}
|
||||||
|
return &config
|
||||||
|
}
|
||||||
|
|
||||||
|
func kubeconfigProxyFunc(t *testing.T, squidProxyURL string) func(req *http.Request) (*url.URL, error) {
|
||||||
|
return func(req *http.Request) (*url.URL, error) {
|
||||||
|
t.Helper()
|
||||||
|
parsedSquidProxyURL, err := url.Parse(squidProxyURL)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Logf("passing request for %s through proxy %s", req.URL, parsedSquidProxyURL.String())
|
||||||
|
return parsedSquidProxyURL, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func configMapForConfig(t *testing.T, env *library.TestEnv, config impersonator.Config) corev1.ConfigMap {
|
func configMapForConfig(t *testing.T, env *library.TestEnv, config impersonator.Config) corev1.ConfigMap {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
configString, err := yaml.Marshal(config)
|
configString, err := yaml.Marshal(config)
|
||||||
|
@ -443,11 +443,3 @@ func TestWhoAmI_ImpersonateDirectly(t *testing.T) {
|
|||||||
whoAmIAnonymous,
|
whoAmIAnonymous,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWhoAmI_ImpersonateViaProxy(t *testing.T) {
|
|
||||||
_ = library.IntegrationEnv(t)
|
|
||||||
|
|
||||||
// TODO: add this test after the impersonation proxy is done
|
|
||||||
// this should test all forms of auth understood by the proxy (certs, SA token, token cred req, anonymous, etc)
|
|
||||||
// remember that impersonation does not support UID: https://github.com/kubernetes/kubernetes/issues/93699
|
|
||||||
}
|
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
||||||
|
|
||||||
auth1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
auth1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
||||||
|
"go.pinniped.dev/generated/latest/apis/concierge/login/v1alpha1"
|
||||||
configv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1"
|
configv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1"
|
||||||
idpv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1"
|
idpv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1"
|
||||||
conciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
|
conciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
|
||||||
@ -54,7 +55,9 @@ func NewClientsetForKubeConfig(t *testing.T, kubeConfig string) kubernetes.Inter
|
|||||||
func NewRestConfigFromKubeconfig(t *testing.T, kubeConfig string) *rest.Config {
|
func NewRestConfigFromKubeconfig(t *testing.T, kubeConfig string) *rest.Config {
|
||||||
kubeConfigFile, err := ioutil.TempFile("", "pinniped-cli-test-*")
|
kubeConfigFile, err := ioutil.TempFile("", "pinniped-cli-test-*")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer os.Remove(kubeConfigFile.Name())
|
defer func() {
|
||||||
|
require.NoError(t, os.Remove(kubeConfigFile.Name()))
|
||||||
|
}()
|
||||||
|
|
||||||
_, err = kubeConfigFile.Write([]byte(kubeConfig))
|
_, err = kubeConfigFile.Write([]byte(kubeConfig))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -423,6 +426,19 @@ func CreateTestClusterRoleBinding(t *testing.T, subject rbacv1.Subject, roleRef
|
|||||||
return created
|
return created
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CreateTokenCredentialRequest(ctx context.Context, t *testing.T, spec v1alpha1.TokenCredentialRequestSpec) (*v1alpha1.TokenCredentialRequest, error) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
client := NewAnonymousConciergeClientset(t)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return client.LoginV1alpha1().TokenCredentialRequests().Create(ctx,
|
||||||
|
&v1alpha1.TokenCredentialRequest{Spec: spec}, metav1.CreateOptions{},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func WaitForUserToHaveAccess(t *testing.T, user string, groups []string, shouldHaveAccessTo *authorizationv1.ResourceAttributes) {
|
func WaitForUserToHaveAccess(t *testing.T, user string, groups []string, shouldHaveAccessTo *authorizationv1.ResourceAttributes) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
client := NewKubernetesClientset(t)
|
client := NewKubernetesClientset(t)
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package library
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
|
|
||||||
"go.pinniped.dev/generated/latest/apis/concierge/login/v1alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func CreateTokenCredentialRequest(ctx context.Context, t *testing.T, spec v1alpha1.TokenCredentialRequestSpec) (*v1alpha1.TokenCredentialRequest, error) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
client := NewAnonymousConciergeClientset(t)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, time.Minute)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
return client.LoginV1alpha1().TokenCredentialRequests().Create(ctx,
|
|
||||||
&v1alpha1.TokenCredentialRequest{Spec: spec}, v1.CreateOptions{},
|
|
||||||
)
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user