e2e test: PINNIPED_USERNAME/PINNIPED_PASSWORD env vars during LDAP login
This commit is contained in:
parent
cac45fd999
commit
deb699a84a
@ -249,15 +249,16 @@ func TestE2EFullIntegration(t *testing.T) {
|
|||||||
// It should now be in the "success" state.
|
// It should now be in the "success" state.
|
||||||
formpostExpectSuccessState(t, page)
|
formpostExpectSuccessState(t, page)
|
||||||
|
|
||||||
// Expect the CLI to output a list of namespaces in JSON format.
|
// Expect the CLI to output a list of namespaces.
|
||||||
t.Logf("waiting for kubectl to output namespace list JSON")
|
t.Logf("waiting for kubectl to output namespace list")
|
||||||
var kubectlOutput string
|
var kubectlOutput string
|
||||||
select {
|
select {
|
||||||
case <-time.After(10 * time.Second):
|
case <-time.After(10 * time.Second):
|
||||||
require.Fail(t, "timed out waiting for kubectl output")
|
require.Fail(t, "timed out waiting for kubectl output")
|
||||||
case kubectlOutput = <-kubectlOutputChan:
|
case kubectlOutput = <-kubectlOutputChan:
|
||||||
}
|
}
|
||||||
require.Greaterf(t, len(strings.Split(kubectlOutput, "\n")), 2, "expected some namespaces to be returned, got %q", kubectlOutput)
|
requireKubectlGetNamespaceOutput(t, env, kubectlOutput)
|
||||||
|
|
||||||
t.Logf("first kubectl command took %s", time.Since(start).String())
|
t.Logf("first kubectl command took %s", time.Since(start).String())
|
||||||
|
|
||||||
requireUserCanUseKubectlWithoutAuthenticatingAgain(ctx, t, env,
|
requireUserCanUseKubectlWithoutAuthenticatingAgain(ctx, t, env,
|
||||||
@ -364,10 +365,11 @@ func TestE2EFullIntegration(t *testing.T) {
|
|||||||
|
|
||||||
// Read all of the remaining output from the subprocess until EOF.
|
// Read all of the remaining output from the subprocess until EOF.
|
||||||
t.Logf("waiting for kubectl to output namespace list")
|
t.Logf("waiting for kubectl to output namespace list")
|
||||||
remainingOutput, _ := ioutil.ReadAll(ptyFile)
|
// Read all of the output from the subprocess until EOF.
|
||||||
// Ignore any errors returned because there is always an error on linux.
|
// Ignore any errors returned because there is always an error on linux.
|
||||||
require.Greaterf(t, len(remainingOutput), 0, "expected to get some more output from the kubectl subcommand, but did not")
|
kubectlOutputBytes, _ := ioutil.ReadAll(ptyFile)
|
||||||
require.Greaterf(t, len(strings.Split(string(remainingOutput), "\n")), 2, "expected some namespaces to be returned, got %q", string(remainingOutput))
|
requireKubectlGetNamespaceOutput(t, env, string(kubectlOutputBytes))
|
||||||
|
|
||||||
t.Logf("first kubectl command took %s", time.Since(start).String())
|
t.Logf("first kubectl command took %s", time.Since(start).String())
|
||||||
|
|
||||||
requireUserCanUseKubectlWithoutAuthenticatingAgain(ctx, t, env,
|
requireUserCanUseKubectlWithoutAuthenticatingAgain(ctx, t, env,
|
||||||
@ -380,8 +382,9 @@ func TestE2EFullIntegration(t *testing.T) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add an LDAP upstream IDP and try using it to authenticate during kubectl commands.
|
// Add an LDAP upstream IDP and try using it to authenticate during kubectl commands
|
||||||
t.Run("with Supervisor LDAP upstream IDP", func(t *testing.T) {
|
// by interacting with the CLI's username and password prompts.
|
||||||
|
t.Run("with Supervisor LDAP upstream IDP using username and password prompts", func(t *testing.T) {
|
||||||
if len(env.ToolsNamespace) == 0 && !env.HasCapability(testlib.CanReachInternetLDAPPorts) {
|
if len(env.ToolsNamespace) == 0 && !env.HasCapability(testlib.CanReachInternetLDAPPorts) {
|
||||||
t.Skip("LDAP integration test requires connectivity to an LDAP server")
|
t.Skip("LDAP integration test requires connectivity to an LDAP server")
|
||||||
}
|
}
|
||||||
@ -389,12 +392,130 @@ func TestE2EFullIntegration(t *testing.T) {
|
|||||||
expectedUsername := env.SupervisorUpstreamLDAP.TestUserMailAttributeValue
|
expectedUsername := env.SupervisorUpstreamLDAP.TestUserMailAttributeValue
|
||||||
expectedGroups := env.SupervisorUpstreamLDAP.TestUserDirectGroupsDNs
|
expectedGroups := env.SupervisorUpstreamLDAP.TestUserDirectGroupsDNs
|
||||||
|
|
||||||
|
setupClusterForEndToEndLDAPTest(t, expectedUsername, env)
|
||||||
|
|
||||||
|
// Use a specific session cache for this test.
|
||||||
|
sessionCachePath := tempDir + "/ldap-test-sessions.yaml"
|
||||||
|
|
||||||
|
kubeconfigPath := runPinnipedGetKubeconfig(t, env, pinnipedExe, tempDir, []string{
|
||||||
|
"get", "kubeconfig",
|
||||||
|
"--concierge-api-group-suffix", env.APIGroupSuffix,
|
||||||
|
"--concierge-authenticator-type", "jwt",
|
||||||
|
"--concierge-authenticator-name", authenticator.Name,
|
||||||
|
"--oidc-session-cache", sessionCachePath,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Run "kubectl get namespaces" which should trigger an LDAP-style login CLI prompt via the plugin.
|
||||||
|
start := time.Now()
|
||||||
|
kubectlCmd := exec.CommandContext(ctx, "kubectl", "get", "namespace", "--kubeconfig", kubeconfigPath)
|
||||||
|
kubectlCmd.Env = append(os.Environ(), env.ProxyEnv()...)
|
||||||
|
ptyFile, err := pty.Start(kubectlCmd)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Wait for the subprocess to print the username prompt, then type the user's username.
|
||||||
|
readFromFileUntilStringIsSeen(t, ptyFile, "Username: ")
|
||||||
|
_, err = ptyFile.WriteString(expectedUsername + "\n")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Wait for the subprocess to print the password prompt, then type the user's password.
|
||||||
|
readFromFileUntilStringIsSeen(t, ptyFile, "Password: ")
|
||||||
|
_, err = ptyFile.WriteString(env.SupervisorUpstreamLDAP.TestUserPassword + "\n")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Read all of the output from the subprocess until EOF.
|
||||||
|
// Ignore any errors returned because there is always an error on linux.
|
||||||
|
kubectlOutputBytes, _ := ioutil.ReadAll(ptyFile)
|
||||||
|
requireKubectlGetNamespaceOutput(t, env, string(kubectlOutputBytes))
|
||||||
|
|
||||||
|
t.Logf("first kubectl command took %s", time.Since(start).String())
|
||||||
|
|
||||||
|
requireUserCanUseKubectlWithoutAuthenticatingAgain(ctx, t, env,
|
||||||
|
downstream,
|
||||||
|
kubeconfigPath,
|
||||||
|
sessionCachePath,
|
||||||
|
pinnipedExe,
|
||||||
|
expectedUsername,
|
||||||
|
expectedGroups,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add an LDAP upstream IDP and try using it to authenticate during kubectl commands
|
||||||
|
// by passing username and password via environment variables, thus avoiding the CLI's username and password prompts.
|
||||||
|
t.Run("with Supervisor LDAP upstream IDP using PINNIPED_USERNAME and PINNIPED_PASSWORD env vars", func(t *testing.T) {
|
||||||
|
if len(env.ToolsNamespace) == 0 && !env.HasCapability(testlib.CanReachInternetLDAPPorts) {
|
||||||
|
t.Skip("LDAP integration test requires connectivity to an LDAP server")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedUsername := env.SupervisorUpstreamLDAP.TestUserMailAttributeValue
|
||||||
|
expectedGroups := env.SupervisorUpstreamLDAP.TestUserDirectGroupsDNs
|
||||||
|
|
||||||
|
setupClusterForEndToEndLDAPTest(t, expectedUsername, env)
|
||||||
|
|
||||||
|
// Use a specific session cache for this test.
|
||||||
|
sessionCachePath := tempDir + "/ldap-test-with-env-vars-sessions.yaml"
|
||||||
|
|
||||||
|
kubeconfigPath := runPinnipedGetKubeconfig(t, env, pinnipedExe, tempDir, []string{
|
||||||
|
"get", "kubeconfig",
|
||||||
|
"--concierge-api-group-suffix", env.APIGroupSuffix,
|
||||||
|
"--concierge-authenticator-type", "jwt",
|
||||||
|
"--concierge-authenticator-name", authenticator.Name,
|
||||||
|
"--oidc-session-cache", sessionCachePath,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set up the username and password env vars to avoid the interactive prompts.
|
||||||
|
const usernameEnvVar = "PINNIPED_USERNAME"
|
||||||
|
originalUsername, hadOriginalUsername := os.LookupEnv(usernameEnvVar)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if hadOriginalUsername {
|
||||||
|
require.NoError(t, os.Setenv(usernameEnvVar, originalUsername))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
require.NoError(t, os.Setenv(usernameEnvVar, expectedUsername))
|
||||||
|
const passwordEnvVar = "PINNIPED_PASSWORD"
|
||||||
|
originalPassword, hadOriginalPassword := os.LookupEnv(passwordEnvVar)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if hadOriginalPassword {
|
||||||
|
require.NoError(t, os.Setenv(passwordEnvVar, originalPassword))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
require.NoError(t, os.Setenv(passwordEnvVar, env.SupervisorUpstreamLDAP.TestUserPassword))
|
||||||
|
|
||||||
|
// Run "kubectl get namespaces" which should run an LDAP-style login without interactive prompts for username and password.
|
||||||
|
start := time.Now()
|
||||||
|
kubectlCmd := exec.CommandContext(ctx, "kubectl", "get", "namespace", "--kubeconfig", kubeconfigPath)
|
||||||
|
kubectlCmd.Env = append(os.Environ(), env.ProxyEnv()...)
|
||||||
|
ptyFile, err := pty.Start(kubectlCmd)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Read all of the output from the subprocess until EOF.
|
||||||
|
// Ignore any errors returned because there is always an error on linux.
|
||||||
|
kubectlOutputBytes, _ := ioutil.ReadAll(ptyFile)
|
||||||
|
requireKubectlGetNamespaceOutput(t, env, string(kubectlOutputBytes))
|
||||||
|
|
||||||
|
t.Logf("first kubectl command took %s", time.Since(start).String())
|
||||||
|
|
||||||
|
// The next kubectl command should not require auth, so we should be able to run it without these env vars.
|
||||||
|
require.NoError(t, os.Unsetenv(usernameEnvVar))
|
||||||
|
require.NoError(t, os.Unsetenv(passwordEnvVar))
|
||||||
|
|
||||||
|
requireUserCanUseKubectlWithoutAuthenticatingAgain(ctx, t, env,
|
||||||
|
downstream,
|
||||||
|
kubeconfigPath,
|
||||||
|
sessionCachePath,
|
||||||
|
pinnipedExe,
|
||||||
|
expectedUsername,
|
||||||
|
expectedGroups,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupClusterForEndToEndLDAPTest(t *testing.T, username string, env *testlib.TestEnv) {
|
||||||
// Create a ClusterRoleBinding to give our test user from the upstream read-only access to the cluster.
|
// Create a ClusterRoleBinding to give our test user from the upstream read-only access to the cluster.
|
||||||
testlib.CreateTestClusterRoleBinding(t,
|
testlib.CreateTestClusterRoleBinding(t,
|
||||||
rbacv1.Subject{Kind: rbacv1.UserKind, APIGroup: rbacv1.GroupName, Name: expectedUsername},
|
rbacv1.Subject{Kind: rbacv1.UserKind, APIGroup: rbacv1.GroupName, Name: username},
|
||||||
rbacv1.RoleRef{Kind: "ClusterRole", APIGroup: rbacv1.GroupName, Name: "view"},
|
rbacv1.RoleRef{Kind: "ClusterRole", APIGroup: rbacv1.GroupName, Name: "view"},
|
||||||
)
|
)
|
||||||
testlib.WaitForUserToHaveAccess(t, expectedUsername, []string{}, &authorizationv1.ResourceAttributes{
|
testlib.WaitForUserToHaveAccess(t, username, []string{}, &authorizationv1.ResourceAttributes{
|
||||||
Verb: "get",
|
Verb: "get",
|
||||||
Group: "",
|
Group: "",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
@ -434,51 +555,6 @@ func TestE2EFullIntegration(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, idpv1alpha1.LDAPPhaseReady)
|
}, idpv1alpha1.LDAPPhaseReady)
|
||||||
|
|
||||||
// Use a specific session cache for this test.
|
|
||||||
sessionCachePath := tempDir + "/ldap-test-sessions.yaml"
|
|
||||||
|
|
||||||
kubeconfigPath := runPinnipedGetKubeconfig(t, env, pinnipedExe, tempDir, []string{
|
|
||||||
"get", "kubeconfig",
|
|
||||||
"--concierge-api-group-suffix", env.APIGroupSuffix,
|
|
||||||
"--concierge-authenticator-type", "jwt",
|
|
||||||
"--concierge-authenticator-name", authenticator.Name,
|
|
||||||
"--oidc-session-cache", sessionCachePath,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Run "kubectl get namespaces" which should trigger an LDAP-style login CLI prompt via the plugin.
|
|
||||||
start := time.Now()
|
|
||||||
kubectlCmd := exec.CommandContext(ctx, "kubectl", "get", "namespace", "--kubeconfig", kubeconfigPath)
|
|
||||||
kubectlCmd.Env = append(os.Environ(), env.ProxyEnv()...)
|
|
||||||
ptyFile, err := pty.Start(kubectlCmd)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Wait for the subprocess to print the username prompt, then type the user's username.
|
|
||||||
readFromFileUntilStringIsSeen(t, ptyFile, "Username: ")
|
|
||||||
_, err = ptyFile.WriteString(expectedUsername + "\n")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Wait for the subprocess to print the password prompt, then type the user's password.
|
|
||||||
readFromFileUntilStringIsSeen(t, ptyFile, "Password: ")
|
|
||||||
_, err = ptyFile.WriteString(env.SupervisorUpstreamLDAP.TestUserPassword + "\n")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Read all of the remaining output from the subprocess until EOF.
|
|
||||||
remainingOutput, _ := ioutil.ReadAll(ptyFile)
|
|
||||||
// Ignore any errors returned because there is always an error on linux.
|
|
||||||
require.Greaterf(t, len(remainingOutput), 0, "expected to get some more output from the kubectl subcommand, but did not")
|
|
||||||
require.Greaterf(t, len(strings.Split(string(remainingOutput), "\n")), 2, "expected some namespaces to be returned, got %q", string(remainingOutput))
|
|
||||||
t.Logf("first kubectl command took %s", time.Since(start).String())
|
|
||||||
|
|
||||||
requireUserCanUseKubectlWithoutAuthenticatingAgain(ctx, t, env,
|
|
||||||
downstream,
|
|
||||||
kubeconfigPath,
|
|
||||||
sessionCachePath,
|
|
||||||
pinnipedExe,
|
|
||||||
expectedUsername,
|
|
||||||
expectedGroups,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func readFromFileUntilStringIsSeen(t *testing.T, f *os.File, until string) string {
|
func readFromFileUntilStringIsSeen(t *testing.T, f *os.File, until string) string {
|
||||||
@ -510,6 +586,19 @@ func readAvailableOutput(t *testing.T, r io.Reader) (string, bool) {
|
|||||||
return string(buf[:n]), false
|
return string(buf[:n]), false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func requireKubectlGetNamespaceOutput(t *testing.T, env *testlib.TestEnv, kubectlOutput string) {
|
||||||
|
t.Log("kubectl command output:\n", kubectlOutput)
|
||||||
|
require.Greaterf(t, len(kubectlOutput), 0, "expected to get some more output from the kubectl subcommand, but did not")
|
||||||
|
|
||||||
|
// Should look generally like a list of namespaces, with one namespace listed per line in a table format.
|
||||||
|
require.Greaterf(t, len(strings.Split(kubectlOutput, "\n")), 2, "expected some namespaces to be returned, got %q", kubectlOutput)
|
||||||
|
require.Contains(t, kubectlOutput, fmt.Sprintf("\n%s ", env.ConciergeNamespace))
|
||||||
|
require.Contains(t, kubectlOutput, fmt.Sprintf("\n%s ", env.SupervisorNamespace))
|
||||||
|
if len(env.ToolsNamespace) == 0 {
|
||||||
|
require.Contains(t, kubectlOutput, fmt.Sprintf("\n%s ", env.ToolsNamespace))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func requireUserCanUseKubectlWithoutAuthenticatingAgain(
|
func requireUserCanUseKubectlWithoutAuthenticatingAgain(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
|
Loading…
Reference in New Issue
Block a user