diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e6d91c..de4a915 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: needs: image permissions: contents: read - uses: codefly-dev/core/.github/workflows/go-service-ci.yml@25e267bc5b7e346ef6b8439c9a032f115c4795a6 + uses: codefly-dev/core/.github/workflows/go-service-ci.yml@f16e045805dbd213cdbebf98d5f7cbe802496d7c image: permissions: @@ -103,7 +103,10 @@ jobs: smoke service-postgres-root smoke service-postgres-nonroot \ --user 70:70 \ - --tmpfs /var/lib/postgresql/data:uid=70,gid=70 + --read-only \ + --tmpfs /var/lib/postgresql/data:uid=70,gid=70 \ + --tmpfs /var/run/postgresql:uid=70,gid=70 \ + --tmpfs /tmp:uid=70,gid=70 - name: Log in to GitHub Container Registry if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository diff --git a/.github/workflows/releaser.yml b/.github/workflows/releaser.yml index cb1fa10..d3ad99f 100644 --- a/.github/workflows/releaser.yml +++ b/.github/workflows/releaser.yml @@ -61,7 +61,7 @@ jobs: needs: image permissions: contents: read - uses: codefly-dev/core/.github/workflows/go-service-release.yml@25e267bc5b7e346ef6b8439c9a032f115c4795a6 + uses: codefly-dev/core/.github/workflows/go-service-release.yml@f16e045805dbd213cdbebf98d5f7cbe802496d7c secrets: inherit backfill: diff --git a/builder.go b/builder.go index aa03a02..8b60e44 100644 --- a/builder.go +++ b/builder.go @@ -175,41 +175,126 @@ func (s *Builder) Build(ctx context.Context, req *builderv0.BuildRequest) (*buil func (s *Builder) Deploy(ctx context.Context, req *builderv0.DeploymentRequest) (*builderv0.DeploymentResponse, error) { defer s.Wool.Catch() - return s.Builder.DeployKustomize(ctx, req, services.KustomizeDeployment{ + parameters := &DeploymentTemplateParameters{ + WithBootstrap: true, + ManagedImage: s.dockerImage().FullName(), + } + var promotableConfiguration *v0.Configuration + response, err := s.Builder.DeployKustomize(ctx, req, services.KustomizeDeployment{ EnvironmentVariables: s.EnvironmentVariables, Templates: deploymentFS, - Parameters: DeploymentTemplateParameters{ - WithBootstrap: true, - ManagedImage: s.dockerImage().FullName(), - }, + Parameters: parameters, Prepare: func(ctx context.Context, deployment *services.KustomizeDeploymentContext) error { - instance, err := resources.FindNetworkInstanceInNetworkMappings(ctx, req.GetNetworkMappings(), s.TcpEndpoint, resources.NewPublicNetworkAccess()) - if err != nil { - return err + configuration, prepareErr := s.prepareDeployment(ctx, deployment, parameters) + if prepareErr != nil { + return prepareErr } - configuration, err := s.CreateConnectionConfiguration(ctx, req.GetConfiguration(), instance, !s.Settings.WithoutSSL) - if err != nil { - return err - } - ownerConnection, err := s.createOwnerConnectionString(ctx, req.GetConfiguration(), instance.Address, !s.Settings.WithoutSSL) - if err != nil { - return err - } - // These values are private to the Postgres StatefulSet/bootstrap Job. - // Only the capability-scoped configuration above is exported to - // dependent services. - deployment.AddSecrets( - resources.Env("POSTGRES_USER", s.postgresUser), - resources.Env("POSTGRES_PASSWORD", s.postgresPassword), - resources.Env("POSTGRES_DB", s.DatabaseName), - resources.Env("POSTGRES_READ_ONLY_PASSWORD", s.readOnlyPassword), - resources.Env("POSTGRES_READ_WRITE_PASSWORD", s.readWritePassword), - resources.Env(migrationConnectionEnvironmentKey, ownerConnection), - ) s.Wool.Debug("exporting configuration", wool.Field("conf", resources.MakeConfigurationSummary(configuration))) + if deployment.Profile == builderv0.KubernetesOutputProfile_KUBERNETES_OUTPUT_PROFILE_PROMOTABLE_GITOPS_V1 { + promotableConfiguration = configuration + return nil + } return deployment.ExportConfiguration(ctx, configuration) }, }) + if err != nil || + response.GetState().GetState() != builderv0.DeploymentStatus_SUCCESS || + promotableConfiguration == nil { + return response, err + } + response.Configuration = promotableConfiguration + return response, nil +} + +func (s *Builder) prepareDeployment( + ctx context.Context, + deployment *services.KustomizeDeploymentContext, + parameters *DeploymentTemplateParameters, +) (*v0.Configuration, error) { + req := deployment.Request + instance, err := resources.FindNetworkInstanceInNetworkMappings( + ctx, + req.GetNetworkMappings(), + s.TcpEndpoint, + resources.NewPublicNetworkAccess(), + ) + if err != nil { + return nil, err + } + if deployment.Profile == builderv0.KubernetesOutputProfile_KUBERNETES_OUTPUT_PROFILE_PROMOTABLE_GITOPS_V1 { + workloadReferences, referencesErr := selectPromotableSecretReferences( + deployment.Kubernetes.GetSecretReferences(), + ) + if referencesErr != nil { + return nil, referencesErr + } + parameters.StatefulSetSecretReferences = workloadReferences.StatefulSet + parameters.BootstrapJobSecretReferences = workloadReferences.BootstrapJob + return s.promotableConnectionConfiguration(instance), nil + } + + configuration, err := s.CreateConnectionConfiguration(ctx, req.GetConfiguration(), instance, !s.Settings.WithoutSSL) + if err != nil { + return nil, err + } + ownerConnection, err := s.createOwnerConnectionString(ctx, req.GetConfiguration(), instance.Address, !s.Settings.WithoutSSL) + if err != nil { + return nil, err + } + // These raw workload values stay in the ephemeral profile's generated + // Secret; callers receive only the managed-resource configuration. + deployment.AddSecrets( + resources.Env("POSTGRES_USER", s.postgresUser), + resources.Env("POSTGRES_PASSWORD", s.postgresPassword), + resources.Env("POSTGRES_DB", s.DatabaseName), + resources.Env("POSTGRES_READ_ONLY_PASSWORD", s.readOnlyPassword), + resources.Env("POSTGRES_READ_WRITE_PASSWORD", s.readWritePassword), + resources.Env(migrationConnectionEnvironmentKey, ownerConnection), + ) + return configuration, nil +} + +type promotableWorkloadSecretReferences struct { + StatefulSet map[string]*builderv0.KubernetesSecretKeyReference + BootstrapJob map[string]*builderv0.KubernetesSecretKeyReference +} + +func selectPromotableSecretReferences( + references map[string]*builderv0.KubernetesSecretKeyReference, +) (*promotableWorkloadSecretReferences, error) { + statefulSetEnvironmentVariables := []string{ + "POSTGRES_USER", + "POSTGRES_PASSWORD", + "POSTGRES_DB", + } + bootstrapJobEnvironmentVariables := []string{ + "POSTGRES_USER", + "POSTGRES_READ_ONLY_PASSWORD", + "POSTGRES_READ_WRITE_PASSWORD", + migrationConnectionEnvironmentKey, + } + selected := make(map[string]*builderv0.KubernetesSecretKeyReference) + for _, environmentVariable := range append(statefulSetEnvironmentVariables, bootstrapJobEnvironmentVariables...) { + reference := references[environmentVariable] + if reference == nil || reference.GetName() == "" || reference.GetKey() == "" { + return nil, fmt.Errorf("postgres deployment requires a typed Kubernetes Secret reference for %s", environmentVariable) + } + if reference.GetOptional() { + return nil, fmt.Errorf("%s Kubernetes Secret reference must not be optional", environmentVariable) + } + selected[environmentVariable] = reference + } + selectForWorkload := func(environmentVariables []string) map[string]*builderv0.KubernetesSecretKeyReference { + workloadReferences := make(map[string]*builderv0.KubernetesSecretKeyReference, len(environmentVariables)) + for _, environmentVariable := range environmentVariables { + workloadReferences[environmentVariable] = selected[environmentVariable] + } + return workloadReferences + } + return &promotableWorkloadSecretReferences{ + StatefulSet: selectForWorkload(statefulSetEnvironmentVariables), + BootstrapJob: selectForWorkload(bootstrapJobEnvironmentVariables), + }, nil } type create struct { diff --git a/deployment_test.go b/deployment_test.go index d69ba18..49ea663 100644 --- a/deployment_test.go +++ b/deployment_test.go @@ -1,12 +1,18 @@ package main import ( + "context" "os" "path/filepath" "strings" "testing" + "github.com/codefly-dev/core/agents/services" agenttesting "github.com/codefly-dev/core/agents/testing" + basev0 "github.com/codefly-dev/core/generated/go/codefly/base/v0" + builderv0 "github.com/codefly-dev/core/generated/go/codefly/services/builder/v0" + "github.com/codefly-dev/core/resources" + "github.com/stretchr/testify/require" ) func TestDeploymentTemplatesWithMigration(t *testing.T) { @@ -15,6 +21,7 @@ func TestDeploymentTemplatesWithMigration(t *testing.T) { ManagedImage: image.FullName(), }) assertMigrationResource(t, dir, true) + assertEphemeralSecret(t, dir) } func TestDeploymentTemplatesWithoutBootstrap(t *testing.T) { @@ -24,13 +31,261 @@ func TestDeploymentTemplatesWithoutBootstrap(t *testing.T) { assertMigrationResource(t, dir, false) } -func assertMigrationResource(t *testing.T, dir string, expected bool) { +func TestPromotableGitOpsDeploymentReturnsConfigurationAndIsolatesSecrets(t *testing.T) { + useSuccessfulKubectl(t) + builder, networkMappings := newDeploymentTestBuilder(t) + destination := t.TempDir() + + response, err := builder.Deploy(context.Background(), promotableDeploymentRequest( + destination, + networkMappings, + promotablePostgresSecretReferences(), + true, + )) + require.NoError(t, err) + require.Equal(t, builderv0.DeploymentStatus_SUCCESS, response.GetState().GetState(), response.GetState().GetMessage()) + + output := response.GetDeployment().GetKubernetes() + require.Equal(t, builderv0.KubernetesOutputProfile_KUBERNETES_OUTPUT_PROFILE_PROMOTABLE_GITOPS_V1, output.GetProfile()) + require.Equal(t, services.KubernetesManifestContractVersion, output.GetContractVersion()) + require.Equal(t, builderv0.KubernetesManifestValidation_STATUS_PASSED, output.GetValidation().GetStaticValidation()) + require.Equal(t, builderv0.KubernetesManifestValidation_STATUS_PASSED, output.GetValidation().GetServerSideValidation()) + require.True(t, output.GetValidation().GetPromotable()) + + configuration := response.GetConfiguration() + require.Equal(t, builder.Unique(), configuration.GetOrigin()) + require.Equal(t, resources.RuntimeContextFree, configuration.GetRuntimeContext().GetKind()) + require.Len(t, configuration.GetInfos(), 1) + require.Equal(t, "postgres", configuration.GetInfos()[0].GetName()) + values := configuration.GetInfos()[0].GetConfigurationValues() + require.Len(t, values, 2) + for _, key := range []string{readOnlyConnectionKey, readWriteConnectionKey} { + var matched *basev0.ConfigurationValue + for _, value := range values { + if value.GetKey() == key { + matched = value + break + } + } + require.NotNil(t, matched, "missing configuration value %q", key) + require.True(t, matched.GetSecret(), "configuration value %q is not secret", key) + require.Empty(t, matched.GetValue(), "configuration value %q leaked data", key) + } + for _, value := range values { + require.NotEqual(t, ownerConnectionKey, value.GetKey()) + } + + baseKustomization := readDeploymentFile(t, destination, "base", "kustomization.yaml") + require.NotContains(t, baseKustomization, "namespace.yaml") + overlayKustomization := readDeploymentFile(t, destination, "overlays", "test", "kustomization.yaml") + require.NotContains(t, overlayKustomization, "secret.yaml") + require.Empty(t, strings.TrimSpace(readDeploymentFile(t, destination, "base", "namespace.yaml"))) + require.Empty(t, strings.TrimSpace(readDeploymentFile(t, destination, "overlays", "test", "secret.yaml"))) + + statefulSet := readDeploymentFile(t, destination, "base", "stateful-set.yaml") + for _, expected := range []string{ + image.FullName(), + "name: POSTGRES_USER", + "name: POSTGRES_PASSWORD", + "name: POSTGRES_DB", + "optional: false", + } { + require.Contains(t, statefulSet, expected) + } + for _, unexpected := range []string{ + "envFrom:", + "name: POSTGRES_READ_ONLY_PASSWORD", + "name: POSTGRES_READ_WRITE_PASSWORD", + "name: " + migrationConnectionEnvironmentKey, + "name: UNRELATED_SECRET", + } { + require.NotContains(t, statefulSet, unexpected) + } + + job := readDeploymentFile(t, destination, "base", "job.yaml") + for _, expected := range []string{ + "registry.example.com/module/postgres@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "name: POSTGRES_USER", + "name: POSTGRES_READ_ONLY_PASSWORD", + "name: POSTGRES_READ_WRITE_PASSWORD", + "name: " + migrationConnectionEnvironmentKey, + "optional: false", + } { + require.Contains(t, job, expected) + } + for _, unexpected := range []string{ + "envFrom:", + "name: POSTGRES_PASSWORD", + "name: POSTGRES_DB", + "name: UNRELATED_SECRET", + } { + require.NotContains(t, job, unexpected) + } +} + +func TestPromotableGitOpsDeploymentRejectsMissingOrOptionalRequiredSecretReferences(t *testing.T) { + required := []string{ + "POSTGRES_USER", + "POSTGRES_PASSWORD", + "POSTGRES_DB", + "POSTGRES_READ_ONLY_PASSWORD", + "POSTGRES_READ_WRITE_PASSWORD", + migrationConnectionEnvironmentKey, + } + for _, environmentVariable := range required { + t.Run("missing/"+environmentVariable, func(t *testing.T) { + builder, networkMappings := newDeploymentTestBuilder(t) + references := promotablePostgresSecretReferences() + delete(references, environmentVariable) + + response, err := builder.Deploy(context.Background(), promotableDeploymentRequest( + t.TempDir(), + networkMappings, + references, + false, + )) + require.NoError(t, err) + require.Equal(t, builderv0.DeploymentStatus_ERROR, response.GetState().GetState()) + require.Contains(t, response.GetState().GetMessage(), "requires a typed Kubernetes Secret reference for "+environmentVariable) + require.Nil(t, response.GetConfiguration()) + }) + + t.Run("optional/"+environmentVariable, func(t *testing.T) { + builder, networkMappings := newDeploymentTestBuilder(t) + references := promotablePostgresSecretReferences() + references[environmentVariable].Optional = true + + response, err := builder.Deploy(context.Background(), promotableDeploymentRequest( + t.TempDir(), + networkMappings, + references, + false, + )) + require.NoError(t, err) + require.Equal(t, builderv0.DeploymentStatus_ERROR, response.GetState().GetState()) + require.Contains(t, response.GetState().GetMessage(), environmentVariable+" Kubernetes Secret reference must not be optional") + require.Nil(t, response.GetConfiguration()) + }) + } +} + +func newDeploymentTestBuilder(t *testing.T) (*Builder, []*basev0.NetworkMapping) { t.Helper() - content, err := os.ReadFile(filepath.Join(dir, "base", "kustomization.yaml")) - if err != nil { - t.Fatal(err) + ctx := context.Background() + builder := NewBuilder() + identity := &basev0.ServiceIdentity{ + Name: "postgres", + Module: "module", + Workspace: "workspace", + Version: "1.2.3", } - if got := strings.Contains(string(content), "- job.yaml"); got != expected { + require.NoError(t, builder.Base.HeadlessLoad(ctx, identity)) + builder.Base.Information = &services.Information{ + Service: resources.ToServiceWithCase(builder.Identity), + Module: resources.ToModuleWithCase(builder.Identity), + } + builder.EnvironmentVariables.SetIdentity(identity) + builder.TcpEndpoint = &basev0.Endpoint{ + Name: "tcp", + Module: identity.Module, + Service: identity.Name, + Api: "tcp", + } + instance := resources.NewNetworkInstance("postgres.example.com", 5432) + instance.Access = resources.NewPublicNetworkAccess() + return builder, []*basev0.NetworkMapping{{ + Endpoint: builder.TcpEndpoint, + Instances: []*basev0.NetworkInstance{instance}, + }} +} + +func promotableDeploymentRequest( + destination string, + networkMappings []*basev0.NetworkMapping, + secretReferences map[string]*builderv0.KubernetesSecretKeyReference, + validateServerSide bool, +) *builderv0.DeploymentRequest { + const digest = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + return &builderv0.DeploymentRequest{ + Environment: &basev0.Environment{Name: "test"}, + NetworkMappings: networkMappings, + Deployment: &builderv0.Deployment{ + Kind: &builderv0.Deployment_Kubernetes{ + Kubernetes: &builderv0.KubernetesDeployment{ + Namespace: "codefly-test", + Destination: destination, + BuildContext: &builderv0.DockerBuildContext{DockerRepository: "registry.example.com", ImageDigest: digest}, + Profile: builderv0.KubernetesOutputProfile_KUBERNETES_OUTPUT_PROFILE_PROMOTABLE_GITOPS_V1, + SecretReferences: secretReferences, + ValidateServerSide: validateServerSide, + }, + }, + }, + } +} + +func promotablePostgresSecretReferences() map[string]*builderv0.KubernetesSecretKeyReference { + return map[string]*builderv0.KubernetesSecretKeyReference{ + "POSTGRES_USER": { + Name: "postgres-stateful-set", + Key: "username", + }, + "POSTGRES_PASSWORD": { + Name: "postgres-stateful-set", + Key: "password", + }, + "POSTGRES_DB": { + Name: "postgres-stateful-set", + Key: "database", + }, + "POSTGRES_READ_ONLY_PASSWORD": { + Name: "postgres-bootstrap", + Key: "read-only-password", + }, + "POSTGRES_READ_WRITE_PASSWORD": { + Name: "postgres-bootstrap", + Key: "read-write-password", + }, + migrationConnectionEnvironmentKey: { + Name: "postgres-bootstrap", + Key: "migration-connection", + }, + "UNRELATED_SECRET": { + Name: "unrelated-secret", + Key: "token", + }, + } +} + +func useSuccessfulKubectl(t *testing.T) { + t.Helper() + bin := t.TempDir() + kubectl := filepath.Join(bin, "kubectl") + require.NoError(t, os.WriteFile(kubectl, []byte("#!/bin/sh\ncat >/dev/null\n"), 0o755)) + t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH")) +} + +func assertMigrationResource(t *testing.T, dir string, expected bool) { + t.Helper() + content := readDeploymentFile(t, dir, "base", "kustomization.yaml") + if got := strings.Contains(content, "- job.yaml"); got != expected { t.Fatalf("migration resource present = %t, want %t:\n%s", got, expected, content) } } + +func assertEphemeralSecret(t *testing.T, dir string) { + t.Helper() + require.Contains(t, readDeploymentFile(t, dir, "base", "kustomization.yaml"), "- namespace.yaml") + require.Contains(t, readDeploymentFile(t, dir, "base", "namespace.yaml"), "kind: Namespace") + require.Contains(t, readDeploymentFile(t, dir, "overlays", "test", "kustomization.yaml"), "- secret.yaml") + secret := readDeploymentFile(t, dir, "overlays", "test", "secret.yaml") + require.Contains(t, secret, "kind: Secret") + require.Contains(t, secret, "CODEFLY_TEST_SECRET: c2VjcmV0") +} + +func readDeploymentFile(t *testing.T, directory string, elements ...string) string { + t.Helper() + content, err := os.ReadFile(filepath.Join(append([]string{directory}, elements...)...)) + require.NoError(t, err) + return string(content) +} diff --git a/go.mod b/go.mod index e8f9a86..bea6016 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/codefly-dev/service-postgres go 1.25.12 -toolchain go1.26.4 +toolchain go1.26.5 require ( github.com/codefly-dev/core v0.2.51 diff --git a/main.go b/main.go index fda5fdb..344082e 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,7 @@ import ( "github.com/codefly-dev/core/builders" basev0 "github.com/codefly-dev/core/generated/go/codefly/base/v0" agentv0 "github.com/codefly-dev/core/generated/go/codefly/services/agent/v0" + builderv0 "github.com/codefly-dev/core/generated/go/codefly/services/builder/v0" "github.com/codefly-dev/core/resources" runnersbase "github.com/codefly-dev/core/runners/base" "github.com/codefly-dev/core/shared" @@ -139,8 +140,10 @@ func parseRuntimeImageLock(content []byte) (*resources.DockerImage, error) { } type DeploymentTemplateParameters struct { - WithBootstrap bool - ManagedImage string + WithBootstrap bool + ManagedImage string + StatefulSetSecretReferences map[string]*builderv0.KubernetesSecretKeyReference + BootstrapJobSecretReferences map[string]*builderv0.KubernetesSecretKeyReference } // defaultExtensions are CREATE EXTENSION'd on every start (best-effort). They @@ -290,6 +293,25 @@ func (s *Service) CreateConnectionConfiguration(ctx context.Context, conf *basev return outputConf, nil } +// promotableConnectionConfiguration describes the capability-scoped handoff +// without embedding credentials. The migration owner remains private to the +// bootstrap Job and is never advertised to dependent workloads. +func (s *Service) promotableConnectionConfiguration(instance *basev0.NetworkInstance) *basev0.Configuration { + return &basev0.Configuration{ + Origin: s.Base.Unique(), + RuntimeContext: resources.RuntimeContextFromInstance(instance), + Infos: []*basev0.ConfigurationInformation{ + { + Name: "postgres", + ConfigurationValues: []*basev0.ConfigurationValue{ + {Key: readOnlyConnectionKey, Secret: true}, + {Key: readWriteConnectionKey, Secret: true}, + }, + }, + }, + } +} + func postgresConnectionString(address, database, user, password string, withSSL bool) string { query := url.Values{} if !withSSL || strings.Contains(address, "localhost") || strings.Contains(address, "host.docker.internal") { diff --git a/templates/deployment/kustomize/base/job.yaml.tmpl b/templates/deployment/kustomize/base/job.yaml.tmpl index 27cac89..a0f91e8 100644 --- a/templates/deployment/kustomize/base/job.yaml.tmpl +++ b/templates/deployment/kustomize/base/job.yaml.tmpl @@ -24,25 +24,27 @@ spec: securityContext: allowPrivilegeEscalation: false runAsNonRoot: true + runAsUser: 65534 + runAsGroup: 65534 + readOnlyRootFilesystem: true capabilities: drop: - ALL - readOnlyRootFilesystem: true seccompProfile: type: RuntimeDefault {{- if not .GitOps }} envFrom: - secretRef: name: secret-{{ .Service.Name.DNSCase }} -{{- end }} -{{- if .GitOps }} +{{- else }} env: -{{- range $key, $reference := .SecretReferences }} - - name: {{ $key }} +{{- range $environmentVariable, $reference := .Deployment.Parameters.BootstrapJobSecretReferences }} + - name: {{ $environmentVariable }} valueFrom: secretKeyRef: name: {{ $reference.Name }} key: {{ $reference.Key }} + optional: {{ $reference.Optional }} {{- end }} {{- end }} resources: diff --git a/templates/deployment/kustomize/base/namespace.yaml.tmpl b/templates/deployment/kustomize/base/namespace.yaml.tmpl index 9117d93..6f6d220 100644 --- a/templates/deployment/kustomize/base/namespace.yaml.tmpl +++ b/templates/deployment/kustomize/base/namespace.yaml.tmpl @@ -4,6 +4,6 @@ kind: Namespace metadata: name: "{{ .Namespace }}" labels: - istio-injection: "enabled" app.kubernetes.io/managed-by: codefly + istio-injection: "enabled" {{- end }} diff --git a/templates/deployment/kustomize/base/stateful-set.yaml.tmpl b/templates/deployment/kustomize/base/stateful-set.yaml.tmpl index 9db5b60..b37c1e5 100644 --- a/templates/deployment/kustomize/base/stateful-set.yaml.tmpl +++ b/templates/deployment/kustomize/base/stateful-set.yaml.tmpl @@ -50,10 +50,10 @@ spec: allowPrivilegeEscalation: false runAsNonRoot: true runAsUser: 70 + readOnlyRootFilesystem: true capabilities: drop: - ALL - readOnlyRootFilesystem: true seccompProfile: type: RuntimeDefault ports: @@ -63,15 +63,15 @@ spec: envFrom: - secretRef: name: secret-{{ .Service.Name.DNSCase }} -{{- end }} -{{- if .GitOps }} +{{- else }} env: -{{- range $key, $reference := .SecretReferences }} - - name: {{ $key }} +{{- range $environmentVariable, $reference := .Deployment.Parameters.StatefulSetSecretReferences }} + - name: {{ $environmentVariable }} valueFrom: secretKeyRef: name: {{ $reference.Name }} key: {{ $reference.Key }} + optional: {{ $reference.Optional }} {{- end }} {{- end }} # Modest defaults — tune in overlay per-tenant. The PSS diff --git a/workflow_test.go b/workflow_test.go index 240bd99..4dabb5f 100644 --- a/workflow_test.go +++ b/workflow_test.go @@ -41,6 +41,11 @@ func TestCIWorkflowValidatesLockedImageForEveryPullRequest(t *testing.T) { unitTests := findWorkflowStep(t, workflow.Jobs["image"], "Run unit tests") require.Empty(t, unitTests.If) + smoke := findWorkflowStep(t, workflow.Jobs["image"], "Smoke test runtime image") + require.Contains(t, smoke.Run, "--read-only") + require.Contains(t, smoke.Run, "--tmpfs /var/run/postgresql:uid=70,gid=70") + require.Contains(t, smoke.Run, "--tmpfs /tmp:uid=70,gid=70") + verify := findWorkflowStep(t, workflow.Jobs["image"], "Verify published runtime image") require.Contains(t, verify.Run, `--config "$anonymous_docker_config"`) require.Contains(t, verify.Run, `"$RUNTIME_IMAGE"`)