mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-01-10 07:58:07 +00:00
Force the use of secure TLS config
This change updates the TLS config used by all pinniped components. There are no configuration knobs associated with this change. Thus this change tightens our static defaults. There are four TLS config levels: 1. Secure (TLS 1.3 only) 2. Default (TLS 1.2+ best ciphers that are well supported) 3. Default LDAP (TLS 1.2+ with less good ciphers) 4. Legacy (currently unused, TLS 1.2+ with all non-broken ciphers) Highlights per component: 1. pinniped CLI - uses "secure" config against KAS - uses "default" for all other connections 2. concierge - uses "secure" config as an aggregated API server - uses "default" config as a impersonation proxy API server - uses "secure" config against KAS - uses "default" config for JWT authenticater (mostly, see code) - no changes to webhook authenticater (see code) 3. supervisor - uses "default" config as a server - uses "secure" config against KAS - uses "default" config against OIDC IDPs - uses "default LDAP" config against LDAP IDPs Signed-off-by: Monis Khan <mok@vmware.com>
This commit is contained in:
@@ -108,7 +108,14 @@ func TestCLIGetKubeconfigStaticToken_Parallel(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func runPinnipedCLI(t *testing.T, envVars []string, pinnipedExe string, args ...string) (string, string) {
|
||||
type testingT interface {
|
||||
Helper()
|
||||
Errorf(format string, args ...interface{})
|
||||
FailNow()
|
||||
Logf(format string, args ...interface{})
|
||||
}
|
||||
|
||||
func runPinnipedCLI(t testingT, envVars []string, pinnipedExe string, args ...string) (string, string) {
|
||||
t.Helper()
|
||||
start := time.Now()
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
@@ -45,8 +45,10 @@ func TestUnsuccessfulCredentialRequest_Parallel(t *testing.T) {
|
||||
require.Equal(t, "authentication failed", *response.Status.Message)
|
||||
}
|
||||
|
||||
// TCRs are non-mutating and safe to run in parallel with serial tests, see main_test.go.
|
||||
func TestSuccessfulCredentialRequest_Parallel(t *testing.T) {
|
||||
// TestSuccessfulCredentialRequest cannot run in parallel because runPinnipedLoginOIDC uses a fixed port
|
||||
// for its localhost listener via --listen-port=env.CLIUpstreamOIDC.CallbackURL.Port() per oidcLoginCommand.
|
||||
// Since ports are global to the process, tests using oidcLoginCommand must be run serially.
|
||||
func TestSuccessfulCredentialRequest(t *testing.T) {
|
||||
env := testlib.IntegrationEnv(t).WithCapability(testlib.ClusterSigningKeyIsAvailable)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 6*time.Minute)
|
||||
|
||||
@@ -65,6 +65,7 @@ import (
|
||||
identityv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/identity/v1alpha1"
|
||||
loginv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/login/v1alpha1"
|
||||
pinnipedconciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
|
||||
"go.pinniped.dev/internal/crypto/ptls"
|
||||
"go.pinniped.dev/internal/httputil/roundtripper"
|
||||
"go.pinniped.dev/internal/kubeclient"
|
||||
"go.pinniped.dev/internal/testutil"
|
||||
@@ -305,20 +306,18 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
||||
Verb: "get", Group: "", Version: "v1", Resource: "namespaces",
|
||||
})
|
||||
|
||||
// Get pods in concierge namespace and pick one.
|
||||
// this is for tests that require performing actions against a running pod. We use the concierge pod because we already have it handy.
|
||||
// We want to make sure it's a concierge pod (not cert agent), because we need to be able to port-forward a running port.
|
||||
pods, err := adminClient.CoreV1().Pods(env.ConciergeNamespace).List(ctx, metav1.ListOptions{})
|
||||
// Get pods in supervisor namespace and pick one.
|
||||
// this is for tests that require performing actions against a running pod.
|
||||
// We use the supervisor pod because we already have it handy and need to port-forward a running port.
|
||||
// We avoid using the concierge for this because it requires TLS 1.3 which is not support by older versions of curl.
|
||||
supervisorPods, err := adminClient.CoreV1().Pods(env.SupervisorNamespace).List(ctx,
|
||||
metav1.ListOptions{LabelSelector: "deployment.pinniped.dev=supervisor"})
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(pods.Items), 0)
|
||||
var conciergePod *corev1.Pod
|
||||
for _, pod := range pods.Items {
|
||||
pod := pod
|
||||
if !strings.Contains(pod.Name, "kube-cert-agent") {
|
||||
conciergePod = &pod
|
||||
}
|
||||
}
|
||||
require.NotNil(t, conciergePod, "could not find a concierge pod")
|
||||
require.NotEmpty(t, supervisorPods.Items, "could not find supervisor pods")
|
||||
supervisorPod := supervisorPods.Items[0]
|
||||
|
||||
// make sure the supervisor has a default TLS cert during this test so that it can handle a TLS connection
|
||||
_ = createTLSCertificateSecret(ctx, t, env.SupervisorNamespace, "cert-hostname-doesnt-matter", nil, defaultTLSCertSecretName(env), adminClient)
|
||||
|
||||
// Test that the user can perform basic actions through the client with their username and group membership
|
||||
// influencing RBAC checks correctly.
|
||||
@@ -346,7 +345,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
||||
// Run the kubectl port-forward command.
|
||||
timeout, cancelFunc := context.WithTimeout(ctx, 2*time.Minute)
|
||||
defer cancelFunc()
|
||||
portForwardCmd, _, portForwardStderr := kubectlCommand(timeout, t, kubeconfigPath, envVarsWithProxy, "port-forward", "--namespace", env.ConciergeNamespace, conciergePod.Name, "10443:8443")
|
||||
portForwardCmd, _, portForwardStderr := kubectlCommand(timeout, t, kubeconfigPath, envVarsWithProxy, "port-forward", "--namespace", supervisorPod.Namespace, supervisorPod.Name, "10443:8443")
|
||||
portForwardCmd.Env = envVarsWithProxy
|
||||
|
||||
// Start, but don't wait for the command to finish.
|
||||
@@ -366,7 +365,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
||||
defer cancelFunc()
|
||||
startTime := time.Now()
|
||||
for time.Now().Before(startTime.Add(70 * time.Second)) {
|
||||
curlCmd := exec.CommandContext(timeout, "curl", "-k", "-sS", "https://127.0.0.1:10443") // -sS turns off the progressbar but still prints errors
|
||||
curlCmd := exec.CommandContext(timeout, "curl", "-k", "-sS", "https://127.0.0.1:10443/healthz") // -sS turns off the progressbar but still prints errors
|
||||
curlCmd.Stdout = &curlStdOut
|
||||
curlCmd.Stderr = &curlStdErr
|
||||
curlErr := curlCmd.Run()
|
||||
@@ -382,7 +381,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
||||
// curl the endpoint once more, once 70 seconds has elapsed, to make sure the connection is still open.
|
||||
timeout, cancelFunc = context.WithTimeout(ctx, 30*time.Second)
|
||||
defer cancelFunc()
|
||||
curlCmd := exec.CommandContext(timeout, "curl", "-k", "-sS", "https://127.0.0.1:10443") // -sS turns off the progressbar but still prints errors
|
||||
curlCmd := exec.CommandContext(timeout, "curl", "-k", "-sS", "https://127.0.0.1:10443/healthz") // -sS turns off the progressbar but still prints errors
|
||||
curlCmd.Stdout = &curlStdOut
|
||||
curlCmd.Stderr = &curlStdErr
|
||||
curlErr := curlCmd.Run()
|
||||
@@ -392,9 +391,8 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
||||
t.Log("curlStdErr: " + curlStdErr.String())
|
||||
t.Log("stdout: " + curlStdOut.String())
|
||||
}
|
||||
// We expect this to 403, but all we care is that it gets through.
|
||||
require.NoError(t, curlErr)
|
||||
require.Contains(t, curlStdOut.String(), `"forbidden: User \"system:anonymous\" cannot get path \"/\""`)
|
||||
require.Contains(t, curlStdOut.String(), "okokokokok") // a few successful healthz responses
|
||||
})
|
||||
|
||||
t.Run("kubectl port-forward and keeping the connection open for over a minute (idle)", func(t *testing.T) {
|
||||
@@ -404,7 +402,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
||||
// Run the kubectl port-forward command.
|
||||
timeout, cancelFunc := context.WithTimeout(ctx, 2*time.Minute)
|
||||
defer cancelFunc()
|
||||
portForwardCmd, _, portForwardStderr := kubectlCommand(timeout, t, kubeconfigPath, envVarsWithProxy, "port-forward", "--namespace", env.ConciergeNamespace, conciergePod.Name, "10444:8443")
|
||||
portForwardCmd, _, portForwardStderr := kubectlCommand(timeout, t, kubeconfigPath, envVarsWithProxy, "port-forward", "--namespace", supervisorPod.Namespace, supervisorPod.Name, "10444:8443")
|
||||
portForwardCmd.Env = envVarsWithProxy
|
||||
|
||||
// Start, but don't wait for the command to finish.
|
||||
@@ -414,13 +412,13 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
||||
assert.EqualErrorf(t, portForwardCmd.Wait(), "signal: killed", `wanted "kubectl port-forward" to get signaled because context was cancelled (stderr: %q)`, portForwardStderr.String())
|
||||
}()
|
||||
|
||||
// Wait to see if we time out. The default timeout is 60 seconds, but the server should recognize this this
|
||||
// Wait to see if we time out. The default timeout is 60 seconds, but the server should recognize that this
|
||||
// is going to be a long-running command and keep the connection open as long as the client stays connected.
|
||||
time.Sleep(70 * time.Second)
|
||||
|
||||
timeout, cancelFunc = context.WithTimeout(ctx, 2*time.Minute)
|
||||
defer cancelFunc()
|
||||
curlCmd := exec.CommandContext(timeout, "curl", "-k", "-sS", "https://127.0.0.1:10444") // -sS turns off the progressbar but still prints errors
|
||||
curlCmd := exec.CommandContext(timeout, "curl", "-k", "-sS", "https://127.0.0.1:10444/healthz") // -sS turns off the progressbar but still prints errors
|
||||
var curlStdOut, curlStdErr bytes.Buffer
|
||||
curlCmd.Stdout = &curlStdOut
|
||||
curlCmd.Stderr = &curlStdErr
|
||||
@@ -430,9 +428,8 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
||||
t.Log("curlStdErr: " + curlStdErr.String())
|
||||
t.Log("stdout: " + curlStdOut.String())
|
||||
}
|
||||
// We expect this to 403, but all we care is that it gets through.
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, curlStdOut.String(), `"forbidden: User \"system:anonymous\" cannot get path \"/\""`)
|
||||
require.Equal(t, curlStdOut.String(), "ok")
|
||||
})
|
||||
|
||||
t.Run("using and watching all the basic verbs", func(t *testing.T) {
|
||||
@@ -760,7 +757,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
||||
clusterAdminCredentials, impersonationProxyURL, impersonationProxyCACertPEM, nil,
|
||||
)
|
||||
nestedImpersonationUIDOnly.Wrap(func(rt http.RoundTripper) http.RoundTripper {
|
||||
return roundtripper.Func(func(r *http.Request) (*http.Response, error) {
|
||||
return roundtripper.WrapFunc(rt, func(r *http.Request) (*http.Response, error) {
|
||||
r.Header.Set("iMperSONATE-uid", "some-awesome-uid")
|
||||
return rt.RoundTrip(r)
|
||||
})
|
||||
@@ -798,7 +795,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
||||
},
|
||||
)
|
||||
nestedImpersonationUID.Wrap(func(rt http.RoundTripper) http.RoundTripper {
|
||||
return roundtripper.Func(func(r *http.Request) (*http.Response, error) {
|
||||
return roundtripper.WrapFunc(rt, func(r *http.Request) (*http.Response, error) {
|
||||
r.Header.Set("imperSONate-uiD", "some-fancy-uid")
|
||||
return rt.RoundTrip(r)
|
||||
})
|
||||
@@ -1115,7 +1112,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
||||
|
||||
// run the kubectl logs command
|
||||
logLinesCount := 10
|
||||
stdout, err = runKubectl(t, kubeconfigPath, envVarsWithProxy, "logs", "--namespace", conciergePod.Namespace, conciergePod.Name, fmt.Sprintf("--tail=%d", logLinesCount))
|
||||
stdout, err = runKubectl(t, kubeconfigPath, envVarsWithProxy, "logs", "--namespace", supervisorPod.Namespace, supervisorPod.Name, fmt.Sprintf("--tail=%d", logLinesCount))
|
||||
require.NoError(t, err, `"kubectl logs" failed`)
|
||||
// Expect _approximately_ logLinesCount lines in the output
|
||||
// (we can't match 100% exactly due to https://github.com/kubernetes/kubernetes/issues/72628).
|
||||
@@ -1500,6 +1497,20 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("assert impersonator runs with secure TLS config", func(t *testing.T) {
|
||||
parallelIfNotEKS(t)
|
||||
|
||||
cancelCtx, cancel := context.WithCancel(ctx)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
startKubectlPortForward(cancelCtx, t, "10445", "443", env.ConciergeAppName+"-proxy", env.ConciergeNamespace)
|
||||
|
||||
stdout, stderr := runNmapSSLEnum(t, "127.0.0.1", 10445)
|
||||
|
||||
require.Empty(t, stderr)
|
||||
require.Contains(t, stdout, getExpectedCiphers(ptls.Default), "stdout:\n%s", stdout)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("assert correct impersonator service account is being used", func(t *testing.T) {
|
||||
@@ -1956,7 +1967,7 @@ func performImpersonatorDiscovery(ctx context.Context, t *testing.T, env *testli
|
||||
// probe each pod directly for readiness since the concierge status is a lie - it just means a single pod is ready
|
||||
testlib.RequireEventually(t, func(requireEventually *require.Assertions) {
|
||||
pods, err := adminClient.CoreV1().Pods(env.ConciergeNamespace).List(ctx,
|
||||
metav1.ListOptions{LabelSelector: "app=" + env.ConciergeAppName + ",!kube-cert-agent.pinniped.dev"}) // TODO replace with deployment.pinniped.dev=concierge
|
||||
metav1.ListOptions{LabelSelector: "deployment.pinniped.dev=concierge"})
|
||||
requireEventually.NoError(err)
|
||||
requireEventually.Len(pods.Items, 2) // has to stay in sync with the defaults in our YAML
|
||||
|
||||
@@ -2373,7 +2384,7 @@ func getCredForConfig(t *testing.T, config *rest.Config) *loginv1alpha1.ClusterC
|
||||
config = rest.CopyConfig(config)
|
||||
|
||||
config.Wrap(func(rt http.RoundTripper) http.RoundTripper {
|
||||
return roundtripper.Func(func(req *http.Request) (*http.Response, error) {
|
||||
return roundtripper.WrapFunc(rt, func(req *http.Request) (*http.Response, error) {
|
||||
resp, err := rt.RoundTrip(req)
|
||||
|
||||
r := req
|
||||
|
||||
@@ -759,8 +759,6 @@ func startLongRunningCommandAndWaitForInitialOutput(
|
||||
cmd := exec.CommandContext(ctx, command, args...)
|
||||
|
||||
var stdoutBuf, stderrBuf syncBuffer
|
||||
cmd.Stdout = &stdoutBuf
|
||||
cmd.Stderr = &stderrBuf
|
||||
cmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
|
||||
cmd.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)
|
||||
|
||||
|
||||
257
test/integration/securetls_test.go
Normal file
257
test/integration/securetls_test.go
Normal file
@@ -0,0 +1,257 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.pinniped.dev/internal/crypto/ptls"
|
||||
"go.pinniped.dev/internal/testutil/tlsserver"
|
||||
"go.pinniped.dev/test/testlib"
|
||||
)
|
||||
|
||||
// TLS checks safe to run in parallel with serial tests, see main_test.go.
|
||||
func TestSecureTLSPinnipedCLIToKAS_Parallel(t *testing.T) {
|
||||
_ = testlib.IntegrationEnv(t)
|
||||
|
||||
server := tlsserver.TLSTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tlsserver.AssertTLS(t, r, ptls.Secure) // pinniped CLI uses ptls.Secure when talking to KAS
|
||||
w.Header().Set("content-type", "application/json")
|
||||
fmt.Fprint(w, `{"kind":"TokenCredentialRequest","apiVersion":"login.concierge.pinniped.dev/v1alpha1",`+
|
||||
`"status":{"credential":{"token":"some-fancy-token"}}}`)
|
||||
}), tlsserver.RecordTLSHello)
|
||||
|
||||
ca := tlsserver.TLSTestServerCA(server)
|
||||
|
||||
pinnipedExe := testlib.PinnipedCLIPath(t)
|
||||
|
||||
stdout, stderr := runPinnipedCLI(t, nil, pinnipedExe, "login", "static",
|
||||
"--token", "does-not-matter",
|
||||
"--concierge-authenticator-type", "webhook",
|
||||
"--concierge-authenticator-name", "does-not-matter",
|
||||
"--concierge-ca-bundle-data", base64.StdEncoding.EncodeToString(ca),
|
||||
"--concierge-endpoint", server.URL,
|
||||
"--enable-concierge",
|
||||
"--credential-cache", "",
|
||||
)
|
||||
|
||||
require.Empty(t, stderr)
|
||||
require.Equal(t, `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1",`+
|
||||
`"spec":{"interactive":false},"status":{"expirationTimestamp":null,"token":"some-fancy-token"}}
|
||||
`, stdout)
|
||||
}
|
||||
|
||||
// TLS checks safe to run in parallel with serial tests, see main_test.go.
|
||||
func TestSecureTLSPinnipedCLIToSupervisor_Parallel(t *testing.T) {
|
||||
_ = testlib.IntegrationEnv(t)
|
||||
|
||||
server := tlsserver.TLSTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tlsserver.AssertTLS(t, r, ptls.Default) // pinniped CLI uses ptls.Default when talking to supervisor
|
||||
w.Header().Set("content-type", "application/json")
|
||||
fmt.Fprint(w, `{"issuer":"https://not-a-good-issuer"}`)
|
||||
}), tlsserver.RecordTLSHello)
|
||||
|
||||
ca := tlsserver.TLSTestServerCA(server)
|
||||
|
||||
pinnipedExe := testlib.PinnipedCLIPath(t)
|
||||
|
||||
stdout, stderr := runPinnipedCLI(&fakeT{T: t}, nil, pinnipedExe, "login", "oidc",
|
||||
"--ca-bundle-data", base64.StdEncoding.EncodeToString(ca),
|
||||
"--issuer", server.URL,
|
||||
"--credential-cache", "",
|
||||
"--upstream-identity-provider-flow", "cli_password",
|
||||
"--upstream-identity-provider-name", "does-not-matter",
|
||||
"--upstream-identity-provider-type", "oidc",
|
||||
)
|
||||
|
||||
require.Equal(t, `Error: could not complete Pinniped login: could not perform OIDC discovery for "`+
|
||||
server.URL+`": oidc: issuer did not match the issuer returned by provider, expected "`+
|
||||
server.URL+`" got "https://not-a-good-issuer"
|
||||
`, stderr)
|
||||
require.Empty(t, stdout)
|
||||
}
|
||||
|
||||
// TLS checks safe to run in parallel with serial tests, see main_test.go.
|
||||
func TestSecureTLSConciergeAggregatedAPI_Parallel(t *testing.T) {
|
||||
env := testlib.IntegrationEnv(t)
|
||||
|
||||
cancelCtx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
startKubectlPortForward(cancelCtx, t, "10446", "443", env.ConciergeAppName+"-api", env.ConciergeNamespace)
|
||||
|
||||
stdout, stderr := runNmapSSLEnum(t, "127.0.0.1", 10446)
|
||||
|
||||
require.Empty(t, stderr)
|
||||
require.Contains(t, stdout, getExpectedCiphers(ptls.Secure), "stdout:\n%s", stdout)
|
||||
}
|
||||
|
||||
func TestSecureTLSSupervisor(t *testing.T) { // does not run in parallel because of the createTLSCertificateSecret call
|
||||
env := testlib.IntegrationEnv(t)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
adminClient := testlib.NewKubernetesClientset(t)
|
||||
// make sure the supervisor has a default TLS cert during this test so that it can handle a TLS connection
|
||||
_ = createTLSCertificateSecret(ctx, t, env.SupervisorNamespace, "cert-hostname-doesnt-matter", nil, defaultTLSCertSecretName(env), adminClient)
|
||||
|
||||
startKubectlPortForward(ctx, t, "10447", "443", env.SupervisorAppName+"-clusterip", env.SupervisorNamespace)
|
||||
|
||||
stdout, stderr := runNmapSSLEnum(t, "127.0.0.1", 10447)
|
||||
|
||||
// supervisor's cert is ECDSA
|
||||
defaultECDSAOnly := func(rootCAs *x509.CertPool) *tls.Config {
|
||||
c := ptls.Default(rootCAs)
|
||||
ciphers := make([]uint16, 0, len(c.CipherSuites)/2)
|
||||
for _, id := range c.CipherSuites {
|
||||
id := id
|
||||
if !strings.Contains(tls.CipherSuiteName(id), "_ECDSA_") {
|
||||
continue
|
||||
}
|
||||
ciphers = append(ciphers, id)
|
||||
}
|
||||
c.CipherSuites = ciphers
|
||||
return c
|
||||
}
|
||||
|
||||
require.Empty(t, stderr)
|
||||
require.Contains(t, stdout, getExpectedCiphers(defaultECDSAOnly), "stdout:\n%s", stdout)
|
||||
}
|
||||
|
||||
type fakeT struct {
|
||||
*testing.T
|
||||
}
|
||||
|
||||
func (t *fakeT) FailNow() {
|
||||
t.Errorf("fakeT ignored FailNow")
|
||||
}
|
||||
|
||||
func (t *fakeT) Errorf(format string, args ...interface{}) {
|
||||
t.Cleanup(func() {
|
||||
if !t.Failed() {
|
||||
return
|
||||
}
|
||||
t.Logf("reporting previously ignored errors since main test failed:\n"+format, args...)
|
||||
})
|
||||
}
|
||||
|
||||
func runNmapSSLEnum(t *testing.T, host string, port uint16) (string, string) {
|
||||
t.Helper()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
|
||||
version, err := exec.CommandContext(ctx, "nmap", "-V").CombinedOutput()
|
||||
require.NoError(t, err)
|
||||
|
||||
versionMatches := regexp.MustCompile(`Nmap version 7\.(?P<minor>\d+)`).FindStringSubmatch(string(version))
|
||||
require.Len(t, versionMatches, 2)
|
||||
minorVersion, err := strconv.Atoi(versionMatches[1])
|
||||
require.NoError(t, err)
|
||||
require.GreaterOrEqual(t, minorVersion, 92, "nmap >= 7.92.x is required")
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
//nolint:gosec // we are not performing malicious argument injection against ourselves
|
||||
cmd := exec.CommandContext(ctx, "nmap", "--script", "ssl-enum-ciphers",
|
||||
"-p", strconv.FormatUint(uint64(port), 10),
|
||||
host,
|
||||
)
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
require.NoErrorf(t, cmd.Run(), "stderr:\n%s\n\nstdout:\n%s\n\n", stderr.String(), stdout.String())
|
||||
|
||||
return stdout.String(), stderr.String()
|
||||
}
|
||||
|
||||
func getExpectedCiphers(configFunc ptls.ConfigFunc) string {
|
||||
config := configFunc(nil)
|
||||
secureConfig := ptls.Secure(nil)
|
||||
|
||||
skip12 := config.MinVersion == secureConfig.MinVersion
|
||||
|
||||
var tls12Bit, tls13Bit string
|
||||
|
||||
if !skip12 {
|
||||
sort.SliceStable(config.CipherSuites, func(i, j int) bool {
|
||||
a := tls.CipherSuiteName(config.CipherSuites[i])
|
||||
b := tls.CipherSuiteName(config.CipherSuites[j])
|
||||
|
||||
ok1 := strings.Contains(a, "_ECDSA_")
|
||||
ok2 := strings.Contains(b, "_ECDSA_")
|
||||
|
||||
if ok1 && ok2 {
|
||||
return false
|
||||
}
|
||||
|
||||
return ok1
|
||||
})
|
||||
|
||||
var s strings.Builder
|
||||
for i, id := range config.CipherSuites {
|
||||
s.WriteString(fmt.Sprintf(tls12Item, tls.CipherSuiteName(id)))
|
||||
if i == len(config.CipherSuites)-1 {
|
||||
break
|
||||
}
|
||||
s.WriteString("\n")
|
||||
}
|
||||
tls12Bit = fmt.Sprintf(tls12Base, s.String())
|
||||
}
|
||||
|
||||
var s strings.Builder
|
||||
for i, id := range secureConfig.CipherSuites {
|
||||
s.WriteString(fmt.Sprintf(tls13Item, strings.Replace(tls.CipherSuiteName(id), "TLS_", "TLS_AKE_WITH_", 1)))
|
||||
if i == len(secureConfig.CipherSuites)-1 {
|
||||
break
|
||||
}
|
||||
s.WriteString("\n")
|
||||
}
|
||||
tls13Bit = fmt.Sprintf(tls13Base, s.String())
|
||||
|
||||
return fmt.Sprintf(baseItem, tls12Bit, tls13Bit)
|
||||
}
|
||||
|
||||
const (
|
||||
// this surrounds the tls 1.2 and 1.3 text in a way that guarantees that other TLS versions are not supported.
|
||||
baseItem = `/tcp open unknown
|
||||
| ssl-enum-ciphers: %s%s
|
||||
|_ least strength: A
|
||||
|
||||
Nmap done: 1 IP address (1 host up) scanned in`
|
||||
|
||||
// the "cipher preference: client" bit a bug in nmap.
|
||||
// https://github.com/nmap/nmap/issues/1691#issuecomment-536919978
|
||||
tls12Base = `
|
||||
| TLSv1.2:
|
||||
| ciphers:
|
||||
%s
|
||||
| compressors:
|
||||
| NULL
|
||||
| cipher preference: client`
|
||||
|
||||
tls13Base = `
|
||||
| TLSv1.3:
|
||||
| ciphers:
|
||||
%s
|
||||
| cipher preference: server`
|
||||
|
||||
tls12Item = `| %s (secp256r1) - A`
|
||||
tls13Item = `| %s (ecdh_x25519) - A`
|
||||
)
|
||||
@@ -132,7 +132,7 @@ func newClientsetWithConfig(t *testing.T, config *rest.Config) kubernetes.Interf
|
||||
func NewAnonymousClientRestConfig(t *testing.T) *rest.Config {
|
||||
t.Helper()
|
||||
|
||||
return rest.AnonymousClientConfig(NewClientConfig(t))
|
||||
return kubeclient.SecureAnonymousClientConfig(NewClientConfig(t))
|
||||
}
|
||||
|
||||
// Starting with an anonymous client config, add a cert and key to use for authentication in the API server.
|
||||
|
||||
Reference in New Issue
Block a user