From 679c6f3163b7b2d32b029392f32dc183fc9f6fb1 Mon Sep 17 00:00:00 2001 From: Radu Matei Date: Sun, 17 Mar 2024 08:37:32 +0100 Subject: [PATCH] fix(spin/certs): inject CA bunde in the filesystem This commit injects a default CA bundle in the root filesystem for every Spin application pod created, so host components can execute TLS operations. Signed-off-by: Radu Matei --- internal/controller/deployment.go | 33 ++++++++++++++++++- internal/controller/deployment_test.go | 10 +++--- internal/controller/spinapp_controller.go | 9 +++-- .../controller/spinapp_controller_test.go | 2 +- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/internal/controller/deployment.go b/internal/controller/deployment.go index 3a95b373..35928fe3 100644 --- a/internal/controller/deployment.go +++ b/internal/controller/deployment.go @@ -39,11 +39,37 @@ func constructRuntimeConfigSecretMount(_ctx context.Context, secretName string) return volume, volumeMount } +func constructCASecretMount(_ctx context.Context, secretName string) (corev1.Volume, corev1.VolumeMount) { + volume := corev1.Volume{ + Name: "spin-ca", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: secretName, + Optional: ptr(true), + Items: []corev1.KeyToPath{ + { + Key: "ca-certificates.crt", + Path: "ca-certificates.crt", + }, + }, + }, + }, + } + volumeMount := corev1.VolumeMount{ + Name: "spin-ca", + ReadOnly: true, + MountPath: "/etc/ssl/certs/ca-certificates.crt", + SubPath: "ca-certificates.crt", + } + + return volume, volumeMount +} + // ConstructVolumeMountsForApp introspects the application and generates // any required volume mounts. A generated runtime secret is mutually // exclusive with a user-provided secret - this is to require _either_ a // manual runtime-config or a generated one from the crd. -func ConstructVolumeMountsForApp(ctx context.Context, app *spinv1alpha1.SpinApp, generatedRuntimeSecret string) ([]corev1.Volume, []corev1.VolumeMount, error) { +func ConstructVolumeMountsForApp(ctx context.Context, app *spinv1alpha1.SpinApp, generatedRuntimeSecret string, caSecretName string) ([]corev1.Volume, []corev1.VolumeMount, error) { volumes := []corev1.Volume{} volumeMounts := []corev1.VolumeMount{} @@ -67,6 +93,11 @@ func ConstructVolumeMountsForApp(ctx context.Context, app *spinv1alpha1.SpinApp, volumes = append(volumes, app.Spec.Volumes...) volumeMounts = append(volumeMounts, app.Spec.VolumeMounts...) + // TODO: eventually add runtime configuration for specifying the CA bundle to use. + caVolume, caVolumeMount := constructCASecretMount(ctx, caSecretName) + volumes = append(volumes, caVolume) + volumeMounts = append(volumeMounts, caVolumeMount) + return volumes, volumeMounts, nil } diff --git a/internal/controller/deployment_test.go b/internal/controller/deployment_test.go index 97c22aa3..f71901fe 100644 --- a/internal/controller/deployment_test.go +++ b/internal/controller/deployment_test.go @@ -52,14 +52,14 @@ func TestConstructVolumeMountsForApp_Contract(t *testing.T) { // places. app := minimalSpinApp() app.Spec.RuntimeConfig.LoadFromSecret = "a-secret" - _, _, err := ConstructVolumeMountsForApp(context.Background(), app, "a-generated-secret") + _, _, err := ConstructVolumeMountsForApp(context.Background(), app, "a-generated-secret", "a-ca-secret") require.Error(t, err) require.ErrorContains(t, err, "cannot specify both a user-provided runtime secret and a generated one") // No runtime secret at all is ok app = minimalSpinApp() app.Spec.RuntimeConfig.LoadFromSecret = "" - volumes, mounts, err := ConstructVolumeMountsForApp(context.Background(), app, "") + volumes, mounts, err := ConstructVolumeMountsForApp(context.Background(), app, "", "") require.NoError(t, err) require.Empty(t, volumes) require.Empty(t, mounts) @@ -67,7 +67,7 @@ func TestConstructVolumeMountsForApp_Contract(t *testing.T) { // User provided runtime secret is ok app = minimalSpinApp() app.Spec.RuntimeConfig.LoadFromSecret = "foo-secret-v1" - volumes, mounts, err = ConstructVolumeMountsForApp(context.Background(), app, "") + volumes, mounts, err = ConstructVolumeMountsForApp(context.Background(), app, "", "") require.NoError(t, err) require.Len(t, volumes, 1) require.Len(t, mounts, 1) @@ -76,7 +76,7 @@ func TestConstructVolumeMountsForApp_Contract(t *testing.T) { // Generated runtime secret is ok app = minimalSpinApp() app.Spec.RuntimeConfig.LoadFromSecret = "" - volumes, mounts, err = ConstructVolumeMountsForApp(context.Background(), app, "gen-secret") + volumes, mounts, err = ConstructVolumeMountsForApp(context.Background(), app, "gen-secret", "spin-ca") require.NoError(t, err) require.Len(t, volumes, 1) require.Len(t, mounts, 1) @@ -227,7 +227,7 @@ func TestSpinHealthCheckToCoreProbe(t *testing.T) { func TestDeploymentLabel(t *testing.T) { scheme := registerAndGetScheme() app := minimalSpinApp() - deployment, err := constructDeployment(context.Background(), app, &spinv1alpha1.ExecutorDeploymentConfig{}, "", scheme) + deployment, err := constructDeployment(context.Background(), app, &spinv1alpha1.ExecutorDeploymentConfig{}, "", "", scheme) require.Nil(t, err) require.NotNil(t, deployment.ObjectMeta.Labels) diff --git a/internal/controller/spinapp_controller.go b/internal/controller/spinapp_controller.go index 15cc5788..d423698e 100644 --- a/internal/controller/spinapp_controller.go +++ b/internal/controller/spinapp_controller.go @@ -279,7 +279,10 @@ func (r *SpinAppReconciler) reconcileDeployment(ctx context.Context, app *spinv1 } } - desiredDeployment, err := constructDeployment(ctx, app, config, generatedRuntimeConfigSecretName, r.Scheme) + // TODO: make this configurable + caSecretName := "spin-ca" + + desiredDeployment, err := constructDeployment(ctx, app, config, generatedRuntimeConfigSecretName, caSecretName, r.Scheme) if err != nil { return fmt.Errorf("failed to construct Deployment: %w", err) } @@ -346,7 +349,7 @@ func (r *SpinAppReconciler) deleteDeployment(ctx context.Context, app *spinv1alp // constructDeployment builds an appsv1.Deployment based on the configuration of a SpinApp. func constructDeployment(ctx context.Context, app *spinv1alpha1.SpinApp, config *spinv1alpha1.ExecutorDeploymentConfig, - generatedRuntimeConfigSecretName string, scheme *runtime.Scheme) (*appsv1.Deployment, error) { + generatedRuntimeConfigSecretName string, caSecretName string, scheme *runtime.Scheme) (*appsv1.Deployment, error) { // TODO: Once we land admission webhooks write some validation to make // replicas and enableAutoscaling mutually exclusive. var replicas *int32 @@ -356,7 +359,7 @@ func constructDeployment(ctx context.Context, app *spinv1alpha1.SpinApp, config replicas = ptr(app.Spec.Replicas) } - volumes, volumeMounts, err := ConstructVolumeMountsForApp(ctx, app, generatedRuntimeConfigSecretName) + volumes, volumeMounts, err := ConstructVolumeMountsForApp(ctx, app, generatedRuntimeConfigSecretName, caSecretName) if err != nil { return nil, err } diff --git a/internal/controller/spinapp_controller_test.go b/internal/controller/spinapp_controller_test.go index 5621db23..991cdf90 100644 --- a/internal/controller/spinapp_controller_test.go +++ b/internal/controller/spinapp_controller_test.go @@ -402,7 +402,7 @@ func TestConstructDeployment_MinimalApp(t *testing.T) { cfg := &spinv1alpha1.ExecutorDeploymentConfig{ RuntimeClassName: "bananarama", } - deployment, err := constructDeployment(context.Background(), app, cfg, "", nil) + deployment, err := constructDeployment(context.Background(), app, cfg, "", "", nil) require.NoError(t, err) require.NotNil(t, deployment)