From 63d302116a7e75747d01b580d93cf06107004cf2 Mon Sep 17 00:00:00 2001 From: mintaka Date: Wed, 16 Sep 2026 22:23:18 -0400 Subject: [PATCH 1/2] test(stack): seed a throwaway master key for the podman stack tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every podman-tagged test in cmd/compass-stack booted a compass-server with no resolvable at-rest master key, so all nine died at boot on `resolve master key: secrets: resolve: No provider backend configured`. buildUserSecretResolver resolves the key unconditionally and fails closed by design, so a test that boots a server must supply its own. No production change is needed: the supervisor spawns the child with os.Environ(), and compass-server already falls back to $COMPASS_SECRET_PROVIDER, so a t.Setenv in the test process reaches the server. Safe here because the package has no t.Parallel and no TestMain. stackEnv seeds it rather than each caller, because stackEnv snapshots the environment for the subprocess — a provider exported after that call would be captured too late, which is exactly how the --nats-external leg stayed red. Refs RIG-3849 Co-authored-by: Matt Wilkinson --- go/cmd/compass-stack/collector_podman_test.go | 4 ++-- .../container_postgres_podman_test.go | 5 +++-- .../cross_process_podman_test.go | 10 +++++---- .../compass-stack/integration_podman_test.go | 22 ++++++++++++++++++- go/cmd/compass-stack/nats_podman_test.go | 4 ++-- 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/go/cmd/compass-stack/collector_podman_test.go b/go/cmd/compass-stack/collector_podman_test.go index 0a0706207..188fb1f65 100644 --- a/go/cmd/compass-stack/collector_podman_test.go +++ b/go/cmd/compass-stack/collector_podman_test.go @@ -66,7 +66,7 @@ func TestCollectorUpDown(t *testing.T) { binDir := buildBinariesFromModuleRoot(t) stackBin := buildStackBinary(t, binDir) - env := stackEnv(binDir) + env := stackEnv(t, binDir) fx := newContainerFixture(t, shortRoot(t, "-col")) cfg := fx.cfg @@ -143,7 +143,7 @@ func TestExternalOTLPUpDown(t *testing.T) { binDir := buildBinariesFromModuleRoot(t) stackBin := buildStackBinary(t, binDir) - env := stackEnv(binDir) + env := stackEnv(t, binDir) fx := newContainerFixture(t, shortRoot(t, "-extotel")) cfg := fx.cfg diff --git a/go/cmd/compass-stack/container_postgres_podman_test.go b/go/cmd/compass-stack/container_postgres_podman_test.go index a54e023cf..cf1848143 100644 --- a/go/cmd/compass-stack/container_postgres_podman_test.go +++ b/go/cmd/compass-stack/container_postgres_podman_test.go @@ -71,7 +71,7 @@ func TestContainerPostgresUpDown(t *testing.T) { binDir := buildBinariesFromModuleRoot(t) stackBin := buildStackBinary(t, binDir) - env := stackEnv(binDir) + env := stackEnv(t, binDir) fx := newContainerFixture(t, shortRoot(t, "-ctr")) cfg := fx.cfg @@ -144,7 +144,7 @@ func TestExternalDatabaseUpDown(t *testing.T) { binDir := buildBinariesFromModuleRoot(t) stackBin := buildStackBinary(t, binDir) - env := stackEnv(binDir) + env := stackEnv(t, binDir) // The "external" postgres: a throwaway TCP-published container the test owns. externalDSN := startExternalPostgres(t) @@ -238,6 +238,7 @@ func (c containerCfg) args(sub string, extra ...string) []string { // lives under one unique per-pid dir. func newContainerFixture(t *testing.T, root string) containerFixture { t.Helper() + seedMasterKeyProvider(t) pgSockDir := filepath.Join(root, "pgsock") runtimeDir := filepath.Join(root, "rt") serverSock := filepath.Join(root, "s.sock") diff --git a/go/cmd/compass-stack/cross_process_podman_test.go b/go/cmd/compass-stack/cross_process_podman_test.go index 15d0bb9fd..2a74fdbba 100644 --- a/go/cmd/compass-stack/cross_process_podman_test.go +++ b/go/cmd/compass-stack/cross_process_podman_test.go @@ -118,15 +118,13 @@ func TestCrossProcessTeardown(t *testing.T) { if !podmanUsable() { t.Skip("rootless podman not usable in this environment") } - ctx := context.Background() // test root context (rule://go-thread-context exemption for a _test.go root) - // 1. Build the three stack child binaries AND the compass-stack binary itself // into one dir. The subprocesses resolve the children via exec.LookPath, so // the dir must be first on their PATH; compass-stack is invoked by full path. binDir := buildBinariesFromModuleRoot(t) stackBin := buildStackBinary(t, binDir) - env := stackEnv(binDir) + env := stackEnv(t, binDir) // A short-path, free-port config resolved through the SAME resolveConfig the // CLI uses (no duplicated config logic); the fixture's derived socket paths @@ -283,7 +281,11 @@ func buildStackBinary(t *testing.T, binDir string) string { // children (looked up by bare name via exec.LookPath) to the freshly built // binaries. PATH is rebuilt (not merely re-appended) so there is exactly one // PATH entry and binDir is unambiguously first. -func stackEnv(binDir string) []string { +func stackEnv(t *testing.T, binDir string) []string { + t.Helper() + // Seeded here, not left to the caller: this snapshots the environment, so a + // provider exported afterwards would never reach the subprocess. + seedMasterKeyProvider(t) base := os.Environ() out := make([]string, 0, len(base)+1) oldPath := "" diff --git a/go/cmd/compass-stack/integration_podman_test.go b/go/cmd/compass-stack/integration_podman_test.go index 42155dec1..17d984567 100644 --- a/go/cmd/compass-stack/integration_podman_test.go +++ b/go/cmd/compass-stack/integration_podman_test.go @@ -61,6 +61,26 @@ import ( "github.com/RigelBuild/compass/go/internal/stack" ) +// stackMasterKey is a throwaway key for tests that boot compass-server; it fails +// closed without an at-rest key, and boot decoding requires exactly 64 hex chars. +const stackMasterKey = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + +// seedMasterKeyProvider supplies the provider through the test environment because +// a spawned compass-server inherits it, avoiding a test-only CLI flag. Idempotent: +// a test reaches it through both its fixture and stackEnv, and the second call must +// not repoint the provider at an empty file the first one already wrote past. +func seedMasterKeyProvider(t *testing.T) { + t.Helper() + if os.Getenv("COMPASS_SECRET_PROVIDER") != "" { + return + } + secretsPath := filepath.Join(t.TempDir(), "secrets.env") + if err := os.WriteFile(secretsPath, []byte("COMPASS_MASTER_KEY="+stackMasterKey+"\n"), 0o600); err != nil { + t.Fatalf("write secrets file: %v", err) + } + t.Setenv("COMPASS_SECRET_PROVIDER", "dotenv://"+secretsPath) +} + // agentImage is a small, pullable public image standing in for the agent image: // the stack pulls it and hands it to the runner, but never runs it as a // container at up (see the file header). Same image the runtime lifecycle test @@ -146,7 +166,7 @@ type stackFixture struct { // t.TempDir — only the socket/runtime paths are budget-constrained. func newFixture(t *testing.T, shortRoot string) (stackFixture, stack.Deps) { t.Helper() - + seedMasterKeyProvider(t) pgSockDir := filepath.Join(shortRoot, "pg") runtimeDir := filepath.Join(shortRoot, "rt") serverSock := filepath.Join(shortRoot, "s.sock") diff --git a/go/cmd/compass-stack/nats_podman_test.go b/go/cmd/compass-stack/nats_podman_test.go index 4850461b5..83e8a2da2 100644 --- a/go/cmd/compass-stack/nats_podman_test.go +++ b/go/cmd/compass-stack/nats_podman_test.go @@ -60,7 +60,7 @@ func TestNatsUpDown(t *testing.T) { binDir := buildBinariesFromModuleRoot(t) stackBin := buildStackBinary(t, binDir) - env := stackEnv(binDir) + env := stackEnv(t, binDir) fx := newContainerFixture(t, shortRoot(t, "-nats")) cfg := fx.cfg @@ -134,7 +134,7 @@ func TestExternalNatsUpDown(t *testing.T) { binDir := buildBinariesFromModuleRoot(t) stackBin := buildStackBinary(t, binDir) - env := stackEnv(binDir) + env := stackEnv(t, binDir) fx := newContainerFixture(t, shortRoot(t, "-extnats")) cfg := fx.cfg From 6202ce36ea9bfe7f535ef1beee7e8b48f394fe66 Mon Sep 17 00:00:00 2001 From: mintaka Date: Wed, 16 Sep 2026 23:01:38 -0400 Subject: [PATCH 2/2] test(stack): seed the provider unconditionally instead of deferring to ambient MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review finding on the parent. The idempotence guard read os.Getenv, so it deferred to a COMPASS_SECRET_PROVIDER already exported in the developer shell — reinstating the ambient dependency the e2e fix removed, and worse: a real provider would seal a throwaway test stack under a real at-rest key. The guard was also unnecessary. t.TempDir returns a unique dir per call (verified: .../001 vs .../002), so a second seed writes a complete, distinct dotenv with the same key; and t.Setenv restores on cleanup, so it never fired across sequential tests either. Its only live effect was the ambient one. Verified with a hostile export: COMPASS_SECRET_PROVIDER=keyring:// now passes, where the guard would have honored it and failed boot. Also drops an invented hazard from the comment (no mechanism could leave an empty dotenv) and records stackEnv seeding before it snapshots. Refs RIG-3849 Co-authored-by: Matt Wilkinson --- go/cmd/compass-stack/cross_process_podman_test.go | 3 ++- go/cmd/compass-stack/integration_podman_test.go | 15 ++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/go/cmd/compass-stack/cross_process_podman_test.go b/go/cmd/compass-stack/cross_process_podman_test.go index 2a74fdbba..435de4cc9 100644 --- a/go/cmd/compass-stack/cross_process_podman_test.go +++ b/go/cmd/compass-stack/cross_process_podman_test.go @@ -280,7 +280,8 @@ func buildStackBinary(t *testing.T, binDir string) string { // compass-stack subprocess resolves the compass-postgres/-server/-runner // children (looked up by bare name via exec.LookPath) to the freshly built // binaries. PATH is rebuilt (not merely re-appended) so there is exactly one -// PATH entry and binDir is unambiguously first. +// PATH entry and binDir is unambiguously first. It also seeds the master-key +// provider before snapshotting, so the subprocess inherits it. func stackEnv(t *testing.T, binDir string) []string { t.Helper() // Seeded here, not left to the caller: this snapshots the environment, so a diff --git a/go/cmd/compass-stack/integration_podman_test.go b/go/cmd/compass-stack/integration_podman_test.go index 17d984567..fcfe5188e 100644 --- a/go/cmd/compass-stack/integration_podman_test.go +++ b/go/cmd/compass-stack/integration_podman_test.go @@ -61,19 +61,16 @@ import ( "github.com/RigelBuild/compass/go/internal/stack" ) -// stackMasterKey is a throwaway key for tests that boot compass-server; it fails -// closed without an at-rest key, and boot decoding requires exactly 64 hex chars. +// stackMasterKey is a throwaway key for tests that boot compass-server, which +// fails closed without an at-rest key; boot decoding requires exactly 64 hex chars. const stackMasterKey = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" -// seedMasterKeyProvider supplies the provider through the test environment because -// a spawned compass-server inherits it, avoiding a test-only CLI flag. Idempotent: -// a test reaches it through both its fixture and stackEnv, and the second call must -// not repoint the provider at an empty file the first one already wrote past. +// seedMasterKeyProvider rides the test environment because a spawned +// compass-server inherits it, and compass-stack exposes no --secret-provider flag +// to pass one through. Unconditional: honoring an ambient provider would put the +// outcome back at the mercy of the developer's shell. func seedMasterKeyProvider(t *testing.T) { t.Helper() - if os.Getenv("COMPASS_SECRET_PROVIDER") != "" { - return - } secretsPath := filepath.Join(t.TempDir(), "secrets.env") if err := os.WriteFile(secretsPath, []byte("COMPASS_MASTER_KEY="+stackMasterKey+"\n"), 0o600); err != nil { t.Fatalf("write secrets file: %v", err)