Assert the hermetic env replaces the ambient one, not extends it

The shape test passed on `return os.Environ()`; only a Vault-backed
test caught it. Move the helper and its test out from behind the
integration tag and reject any entry outside PATH/HOME/XDG_CONFIG_HOME.
This commit is contained in:
2026-09-22 23:48:59 +10:00
parent 21990d9297
commit ddd93a8b71
3 changed files with 69 additions and 39 deletions
-36
View File
@@ -272,42 +272,6 @@ func writeConfig(t *testing.T, dir, roleID string) string {
return path
}
// hermeticEnv strips the developer's environment from the subprocess. The
// tests claim to exercise the baked-in config path, so a real
// ~/.config/certmanager/config.yaml must not resolve ahead of it and point the
// binary at a production Vault.
func hermeticEnv(t *testing.T) []string {
t.Helper()
empty := t.TempDir()
return []string{
"PATH=/usr/bin:/bin",
"HOME=" + empty,
"XDG_CONFIG_HOME=" + empty,
}
}
// Neither leg of the resolution order announces itself when it is missing: an
// unset HOME or XDG_CONFIG_HOME silently falls through to whatever the
// developer has. Both must therefore be set to somewhere empty, not dropped.
func TestHermeticEnvSetsBothConfigLegs(t *testing.T) {
env := hermeticEnv(t)
for _, key := range []string{"HOME=", "XDG_CONFIG_HOME="} {
idx := slices.IndexFunc(env, func(e string) bool { return strings.HasPrefix(e, key) })
if idx < 0 {
t.Fatalf("%s is unset in the subprocess env: %v", key, env)
}
dir := strings.TrimPrefix(env[idx], key)
if !filepath.IsAbs(dir) {
t.Errorf("%s%s is not an absolute path", key, dir)
}
for _, rel := range []string{"certmanager/config.yaml", ".config/certmanager/config.yaml"} {
if _, err := os.Stat(filepath.Join(dir, rel)); err == nil {
t.Errorf("%s%s already holds %s", key, dir, rel)
}
}
}
}
func runCertmanager(t *testing.T, bin string, args ...string) (int, string, string) {
t.Helper()
cmd := exec.Command(bin, args...)
+3 -3
View File
@@ -1,5 +1,5 @@
// Package integration holds the end-to-end tests that run against a real Vault
// dev server. The tests are behind the `integration` build tag; this file
// keeps the package buildable for a plain `go test ./...`, which otherwise
// fails on a package whose files are all excluded by build constraints.
// dev server. Those tests are behind the `integration` build tag; the
// subprocess environment they share, and its own test, are not, so a plain
// `go test ./...` still guards the helper the tagged tests depend on.
package integration
+66
View File
@@ -0,0 +1,66 @@
package integration
import (
"os"
"path/filepath"
"slices"
"strings"
"testing"
)
// hermeticEnv strips the developer's environment from the subprocess. The
// tests claim to exercise the baked-in config path, so a real
// ~/.config/certmanager/config.yaml must not resolve ahead of it and point the
// binary at a production Vault.
func hermeticEnv(t *testing.T) []string {
t.Helper()
empty := t.TempDir()
return []string{
"PATH=/usr/bin:/bin",
"HOME=" + empty,
"XDG_CONFIG_HOME=" + empty,
}
}
// Neither leg of the resolution order announces itself when it is missing: an
// unset HOME or XDG_CONFIG_HOME silently falls through to whatever the
// developer has. Both must therefore be set to somewhere empty, not dropped.
// Setting them is not enough either: the env has to replace the ambient one
// rather than extend it, so anything the developer exported is asserted gone.
func TestHermeticEnvSetsBothConfigLegs(t *testing.T) {
t.Setenv("CERTMANAGER_AMBIENT_MARKER", "leaked")
t.Setenv("HOME", filepath.Join(t.TempDir(), "developer"))
env := hermeticEnv(t)
allowed := []string{"PATH", "HOME", "XDG_CONFIG_HOME"}
for _, entry := range env {
key, _, ok := strings.Cut(entry, "=")
if !ok {
t.Errorf("%q is not a KEY=VALUE entry", entry)
continue
}
if !slices.Contains(allowed, key) {
t.Errorf("%s is carried over from the ambient environment; want only %v", key, allowed)
}
}
for _, key := range []string{"HOME=", "XDG_CONFIG_HOME="} {
idx := slices.IndexFunc(env, func(e string) bool { return strings.HasPrefix(e, key) })
if idx < 0 {
t.Fatalf("%s is unset in the subprocess env: %v", key, env)
}
dir := strings.TrimPrefix(env[idx], key)
if !filepath.IsAbs(dir) {
t.Errorf("%s%s is not an absolute path", key, dir)
}
if ambient := os.Getenv(strings.TrimSuffix(key, "=")); ambient != "" && dir == ambient {
t.Errorf("%s%s is the ambient value, not a scratch directory", key, dir)
}
for _, rel := range []string{"certmanager/config.yaml", ".config/certmanager/config.yaml"} {
if _, err := os.Stat(filepath.Join(dir, rel)); err == nil {
t.Errorf("%s%s already holds %s", key, dir, rel)
}
}
}
}