Allow configuration of supervisor endpoints

This change allows configuration of the http and https listeners
used by the supervisor.

TCP (IPv4 and IPv6 with any interface and port) and Unix domain
socket based listeners are supported.  Listeners may also be
disabled.

Binding the http listener to TCP addresses other than 127.0.0.1 or
::1 is deprecated.

The deployment now uses https health checks.  The supervisor is
always able to complete a TLS connection with the use of a bootstrap
certificate that is signed by an in-memory certificate authority.

To support sidecar containers used by service meshes, Unix domain
socket based listeners include ACLs that allow writes to the socket
file from any runAsUser specified in the pod's containers.

Signed-off-by: Monis Khan <mok@vmware.com>
This commit is contained in:
Monis Khan
2022-01-18 17:43:45 -05:00
parent 01a7978387
commit 1e1789f6d1
20 changed files with 705 additions and 177 deletions
@@ -316,9 +316,6 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
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
createSupervisorDefaultTLSCertificateSecretIfNeeded(ctx, t)
// Test that the user can perform basic actions through the client with their username and group membership
// influencing RBAC checks correctly.
t.Run(
-3
View File
@@ -108,9 +108,6 @@ func TestSecureTLSSupervisor(t *testing.T) { // does not run in parallel because
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
// make sure the supervisor has a default TLS cert during this test so that it can handle a TLS connection
createSupervisorDefaultTLSCertificateSecretIfNeeded(ctx, t)
startKubectlPortForward(ctx, t, "10447", "443", env.SupervisorAppName+"-nodeport", env.SupervisorNamespace)
stdout, stderr := runNmapSSLEnum(t, "127.0.0.1", 10447)
+22 -25
View File
@@ -9,6 +9,7 @@ import (
"crypto/x509"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
@@ -168,7 +169,7 @@ func TestSupervisorTLSTerminationWithSNI_Disruptive(t *testing.T) {
requireStatus(t, pinnipedClient, federationDomain1.Namespace, federationDomain1.Name, v1alpha1.SuccessFederationDomainStatusCondition)
// The spec.tls.secretName Secret does not exist, so the endpoints should fail with TLS errors.
requireEndpointHasTLSErrorBecauseCertificatesAreNotReady(t, issuer1)
requireEndpointHasBootstrapTLSErrorBecauseCertificatesAreNotReady(t, issuer1)
// Create the Secret.
ca1 := createTLSCertificateSecret(ctx, t, ns, hostname1, nil, certSecretName1, kubeClient)
@@ -189,7 +190,7 @@ func TestSupervisorTLSTerminationWithSNI_Disruptive(t *testing.T) {
}))
// The the endpoints should fail with TLS errors again.
requireEndpointHasTLSErrorBecauseCertificatesAreNotReady(t, issuer1)
requireEndpointHasBootstrapTLSErrorBecauseCertificatesAreNotReady(t, issuer1)
// Create a Secret at the updated name.
ca1update := createTLSCertificateSecret(ctx, t, ns, hostname1, nil, certSecretName1update, kubeClient)
@@ -252,7 +253,7 @@ func TestSupervisorTLSTerminationWithDefaultCerts_Disruptive(t *testing.T) {
requireStatus(t, pinnipedClient, federationDomain1.Namespace, federationDomain1.Name, v1alpha1.SuccessFederationDomainStatusCondition)
// There is no default TLS cert and the spec.tls.secretName was not set, so the endpoints should fail with TLS errors.
requireEndpointHasTLSErrorBecauseCertificatesAreNotReady(t, issuerUsingIPAddress)
requireEndpointHasBootstrapTLSErrorBecauseCertificatesAreNotReady(t, issuerUsingIPAddress)
// Create a Secret at the special name which represents the default TLS cert.
defaultCA := createTLSCertificateSecret(ctx, t, ns, "cert-hostname-doesnt-matter", []net.IP{ips[0]}, defaultTLSCertSecretName(env), kubeClient)
@@ -320,22 +321,6 @@ func createTLSCertificateSecret(ctx context.Context, t *testing.T, ns string, ho
return ca
}
func createSupervisorDefaultTLSCertificateSecretIfNeeded(ctx context.Context, t *testing.T) {
env := testlib.IntegrationEnv(t)
adminClient := testlib.NewKubernetesClientset(t)
ns := env.SupervisorNamespace
name := defaultTLSCertSecretName(env)
_, err := adminClient.CoreV1().Secrets(ns).Get(ctx, name, metav1.GetOptions{})
if k8serrors.IsNotFound(err) {
_ = createTLSCertificateSecret(ctx, t, ns, "cert-hostname-doesnt-matter", nil, name, adminClient)
} else {
require.NoError(t, err)
}
}
func temporarilyRemoveAllFederationDomainsAndDefaultTLSCertSecret(
ctx context.Context,
t *testing.T,
@@ -425,9 +410,14 @@ func requireEndpointNotFound(t *testing.T, url, host, caBundle string) {
}, 2*time.Minute, 200*time.Millisecond)
}
func requireEndpointHasTLSErrorBecauseCertificatesAreNotReady(t *testing.T, url string) {
func requireEndpointHasBootstrapTLSErrorBecauseCertificatesAreNotReady(t *testing.T, url string) {
t.Helper()
httpClient := newHTTPClient(t, "", nil)
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec // there is no way for us to know the bootstrap CA
},
}
testlib.RequireEventually(t, func(requireEventually *require.Assertions) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
@@ -437,11 +427,18 @@ func requireEndpointHasTLSErrorBecauseCertificatesAreNotReady(t *testing.T, url
requireEventually.NoError(err)
response, err := httpClient.Do(request)
if err == nil {
requireEventually.NoError(err)
t.Cleanup(func() {
_ = response.Body.Close()
}
requireEventually.Error(err)
requireEventually.EqualError(err, fmt.Sprintf(`Get "%s": remote error: tls: unrecognized name`, url))
})
requireEventually.Equal(http.StatusInternalServerError, response.StatusCode)
body, err := io.ReadAll(response.Body)
requireEventually.NoError(err)
requireEventually.Equal("pinniped supervisor has invalid TLS serving certificate configuration\n", string(body))
}, 2*time.Minute, 200*time.Millisecond)
}
+40 -6
View File
@@ -5,6 +5,7 @@ package integration
import (
"context"
"crypto/tls"
"fmt"
"io/ioutil"
"net/http"
@@ -32,22 +33,55 @@ func TestSupervisorHealthz(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
requestHealthEndpoint, err := http.NewRequestWithContext(
httpClient := &http.Client{}
httpGet(ctx, t, httpClient, fmt.Sprintf("http://%s/healthz", env.SupervisorHTTPAddress), http.StatusOK, "ok")
}
// Never run this test in parallel since deleting all federation domains and the default TLS secret is disruptive, see main_test.go.
func TestSupervisorHealthzBootstrap_Disruptive(t *testing.T) {
env := testlib.IntegrationEnv(t)
pinnipedClient := testlib.NewSupervisorClientset(t)
kubeClient := testlib.NewKubernetesClientset(t)
ns := env.SupervisorNamespace
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
temporarilyRemoveAllFederationDomainsAndDefaultTLSCertSecret(ctx, t, ns, defaultTLSCertSecretName(env), pinnipedClient, kubeClient)
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec // there is no way for us to know the bootstrap CA
},
}
const badTLSConfigBody = "pinniped supervisor has invalid TLS serving certificate configuration\n"
httpGet(ctx, t, httpClient, fmt.Sprintf("https://%s/healthz", env.SupervisorHTTPSAddress), http.StatusOK, "ok")
httpGet(ctx, t, httpClient, fmt.Sprintf("https://%s", env.SupervisorHTTPSAddress), http.StatusInternalServerError, badTLSConfigBody)
httpGet(ctx, t, httpClient, fmt.Sprintf("https://%s/nothealthz", env.SupervisorHTTPSAddress), http.StatusInternalServerError, badTLSConfigBody)
httpGet(ctx, t, httpClient, fmt.Sprintf("https://%s/healthz/something", env.SupervisorHTTPSAddress), http.StatusInternalServerError, badTLSConfigBody)
}
func httpGet(ctx context.Context, t *testing.T, client *http.Client, url string, expectedStatus int, expectedBody string) {
t.Helper()
req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
fmt.Sprintf("http://%s/healthz", env.SupervisorHTTPAddress),
url,
nil,
)
require.NoError(t, err)
httpClient := &http.Client{}
response, err := httpClient.Do(requestHealthEndpoint) //nolint:bodyclose
response, err := client.Do(req) //nolint:bodyclose
require.NoError(t, err)
require.Equal(t, http.StatusOK, response.StatusCode)
require.Equal(t, expectedStatus, response.StatusCode)
responseBody, err := ioutil.ReadAll(response.Body)
require.NoError(t, err)
err = response.Body.Close()
require.NoError(t, err)
require.Equal(t, "ok", string(responseBody))
require.Equal(t, expectedBody, string(responseBody))
}