Add integration test for using "kubectl exec" through the impersonator

Signed-off-by: Margo Crawford <margaretc@vmware.com>
This commit is contained in:
Ryan Richard 2021-03-05 16:14:45 -08:00 committed by Margo Crawford
parent 73419313ee
commit 49ec16038c
3 changed files with 94 additions and 3 deletions

View File

@ -64,7 +64,7 @@ func TestCLIGetKubeconfigStaticToken(t *testing.T) {
} { } {
tt := tt tt := tt
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
stdout, stderr := runPinnipedCLI(t, pinnipedExe, tt.args...) stdout, stderr := runPinnipedCLI(t, nil, pinnipedExe, tt.args...)
require.Equal(t, tt.expectStderr, stderr) require.Equal(t, tt.expectStderr, stderr)
// Even the deprecated command should now generate a kubeconfig with the new "pinniped login static" command. // Even the deprecated command should now generate a kubeconfig with the new "pinniped login static" command.
@ -99,12 +99,13 @@ func TestCLIGetKubeconfigStaticToken(t *testing.T) {
} }
} }
func runPinnipedCLI(t *testing.T, pinnipedExe string, args ...string) (string, string) { func runPinnipedCLI(t *testing.T, envVars []string, pinnipedExe string, args ...string) (string, string) {
t.Helper() t.Helper()
var stdout, stderr bytes.Buffer var stdout, stderr bytes.Buffer
cmd := exec.Command(pinnipedExe, args...) cmd := exec.Command(pinnipedExe, args...)
cmd.Stdout = &stdout cmd.Stdout = &stdout
cmd.Stderr = &stderr cmd.Stderr = &stderr
cmd.Env = envVars
require.NoErrorf(t, cmd.Run(), "stderr:\n%s\n\nstdout:\n%s\n\n", stderr.String(), stdout.String()) require.NoErrorf(t, cmd.Run(), "stderr:\n%s\n\nstdout:\n%s\n\n", stderr.String(), stdout.String())
return stdout.String(), stderr.String() return stdout.String(), stderr.String()
} }

View File

@ -4,11 +4,17 @@
package integration package integration
import ( import (
"bytes"
"context" "context"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"testing" "testing"
"time" "time"
@ -28,6 +34,7 @@ import (
"go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1" "go.pinniped.dev/generated/latest/apis/concierge/config/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/testutil"
"go.pinniped.dev/internal/testutil/impersonationtoken" "go.pinniped.dev/internal/testutil/impersonationtoken"
"go.pinniped.dev/test/library" "go.pinniped.dev/test/library"
) )
@ -351,6 +358,89 @@ func TestImpersonationProxy(t *testing.T) {
require.EqualError(t, err, expectedErr) require.EqualError(t, err, expectedErr)
}) })
t.Run("kubectl as a client", func(t *testing.T) {
// 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: "edit"},
)
// Wait for the above RBAC rule to take effect.
library.WaitForUserToHaveAccess(t, env.TestUser.ExpectedUsername, []string{}, &v1.ResourceAttributes{
Verb: "get", Group: "", Version: "v1", Resource: "namespaces",
})
pinnipedExe := library.PinnipedCLIPath(t)
tempDir := testutil.TempDir(t)
var envVarsWithProxy []string
if !env.HasCapability(library.HasExternalLoadBalancerProvider) {
// Only if you don't have a load balancer, use the squid proxy when it's available.
envVarsWithProxy = append(os.Environ(), env.ProxyEnv()...)
}
// Get the kubeconfig.
getKubeConfigCmd := []string{"get", "kubeconfig",
"--concierge-api-group-suffix", env.APIGroupSuffix,
"--oidc-skip-browser",
"--static-token", env.TestUser.Token,
// Force the use of impersonation proxy strategy, but let it auto-discover the endpoint and CA.
"--concierge-mode", "ImpersonationProxy"}
t.Log("Running:", pinnipedExe, getKubeConfigCmd)
kubeconfigYAML, getKubeConfigStderr := runPinnipedCLI(t, envVarsWithProxy, pinnipedExe, getKubeConfigCmd...)
// "pinniped get kubectl" prints some status messages to stderr
t.Log(getKubeConfigStderr)
// Make sure that the "pinniped get kubeconfig" auto-discovered the impersonation proxy and we're going to
// make our kubectl requests through the impersonation proxy. Avoid using require.Contains because the error
// message would contain credentials.
require.True(t,
strings.Contains(kubeconfigYAML, "server: "+impersonationProxyURL+"\n"),
"the generated kubeconfig did not include the expected impersonation server address: %s",
impersonationProxyURL,
)
require.True(t,
strings.Contains(kubeconfigYAML, "- --concierge-ca-bundle-data="+base64.StdEncoding.EncodeToString(impersonationProxyCACertPEM)+"\n"),
"the generated kubeconfig did not include the base64 encoded version of this expected impersonation CA cert: %s",
impersonationProxyCACertPEM,
)
// Write the kubeconfig to a temp file.
kubeconfigPath := filepath.Join(tempDir, "kubeconfig.yaml")
require.NoError(t, ioutil.WriteFile(kubeconfigPath, []byte(kubeconfigYAML), 0600))
// Func to run kubeconfig commands.
kubectl := func(args ...string) (string, string, error) {
timeout, cancelFunc := context.WithTimeout(ctx, 2*time.Minute)
defer cancelFunc()
allArgs := append([]string{"--kubeconfig", kubeconfigPath}, args...)
//nolint:gosec // we are not performing malicious argument injection against ourselves
kubectlCmd := exec.CommandContext(timeout, "kubectl", allArgs...)
var stdout, stderr bytes.Buffer
kubectlCmd.Stdout = &stdout
kubectlCmd.Stderr = &stderr
kubectlCmd.Env = envVarsWithProxy
t.Log("starting kubectl subprocess: kubectl", strings.Join(allArgs, " "))
err := kubectlCmd.Run()
t.Logf("kubectl stdout output: %s", stdout.String())
t.Logf("kubectl stderr output: %s", stderr.String())
return stdout.String(), stderr.String(), err
}
// Get pods in concierge namespace and pick one.
// We don't actually care which pod, just want to see that we can "exec echo" in one of them.
pods, err := adminClient.CoreV1().Pods(env.ConciergeNamespace).List(ctx, metav1.ListOptions{})
require.NoError(t, err)
require.Greater(t, len(pods.Items), 0)
podName := pods.Items[0].Name
// Try "kubectl exec" through the impersonation proxy.
echoString := "hello world"
stdout, _, err := kubectl("exec", "--namespace", env.ConciergeNamespace, podName, "--", "echo", echoString)
require.NoError(t, err)
require.Equal(t, stdout, echoString+"\n")
})
// Update configuration to force the proxy to disabled mode // Update configuration to force the proxy to disabled mode
configMap := configMapForConfig(t, env, impersonator.Config{Mode: impersonator.ModeDisabled}) configMap := configMapForConfig(t, env, impersonator.Config{Mode: impersonator.ModeDisabled})
if env.HasCapability(library.HasExternalLoadBalancerProvider) { if env.HasCapability(library.HasExternalLoadBalancerProvider) {

View File

@ -147,7 +147,7 @@ func TestE2EFullIntegration(t *testing.T) {
sessionCachePath := tempDir + "/sessions.yaml" sessionCachePath := tempDir + "/sessions.yaml"
// Run "pinniped get kubeconfig" to get a kubeconfig YAML. // Run "pinniped get kubeconfig" to get a kubeconfig YAML.
kubeconfigYAML, stderr := runPinnipedCLI(t, pinnipedExe, "get", "kubeconfig", kubeconfigYAML, stderr := runPinnipedCLI(t, nil, pinnipedExe, "get", "kubeconfig",
"--concierge-api-group-suffix", env.APIGroupSuffix, "--concierge-api-group-suffix", env.APIGroupSuffix,
"--concierge-authenticator-type", "jwt", "--concierge-authenticator-type", "jwt",
"--concierge-authenticator-name", authenticator.Name, "--concierge-authenticator-name", authenticator.Name,