Resolve typed credentials safely

This commit is contained in:
2026-09-07 04:29:00 +00:00
parent d31ba5a410
commit 92432232ef
34 changed files with 572 additions and 260 deletions

View File

@@ -24,11 +24,8 @@ storage:
if cfg.Pipeline.Storage.S3.RootPrefix != "dnd" {
t.Fatalf("storage.s3.root_prefix = %q, want dnd", cfg.Pipeline.Storage.S3.RootPrefix)
}
if cfg.Pipeline.Storage.S3.AccessKeyIDEnv != DefaultS3AccessKeyIDEnv {
t.Fatalf("storage.s3.access_key_id_env = %q, want %q", cfg.Pipeline.Storage.S3.AccessKeyIDEnv, DefaultS3AccessKeyIDEnv)
}
if cfg.Pipeline.Storage.S3.SecretKeyEnv != DefaultS3SecretAccessKeyEnv {
t.Fatalf("storage.s3.secret_access_key_env = %q, want %q", cfg.Pipeline.Storage.S3.SecretKeyEnv, DefaultS3SecretAccessKeyEnv)
if cfg.Pipeline.Storage.S3.AccessKeyID != nil || cfg.Pipeline.Storage.S3.SecretAccessKey != nil {
t.Fatal("storage.s3 credentials should be optional without implicit references")
}
if cfg.Pipeline.Storage.S3.ForcePathStyle {
t.Fatalf("storage.s3.force_path_style = true, want false default")
@@ -82,14 +79,32 @@ storage:
}
}
func TestLegacyCredentialReferenceFieldsFailStrictDecode(t *testing.T) {
for _, field := range []string{"access_key_id_env", "secret_access_key_env", "llm_api_key_env"} {
t.Run(field, func(t *testing.T) {
pipelineYAML := strings.Replace(testPipelineBaseYAML, "audita:\n binary: audita\n", "audita:\n binary: audita\n "+field+": LEGACY_SECRET\n", 1)
if strings.HasPrefix(field, "access_") || strings.HasPrefix(field, "secret_access_") {
pipelineYAML = testPipelineBaseYAML + "storage:\n backend: s3\n s3:\n bucket: test-bucket\n " + field + ": LEGACY_SECRET\n"
}
pipelinePath, _ := writeConfigFiles(t, pipelineYAML, testSessionBaseYAML)
_, err := LoadPipeline(pipelinePath)
if err == nil || !strings.Contains(err.Error(), field) || !strings.Contains(err.Error(), "strict decode") {
t.Fatalf("LoadPipeline() error = %v, want strict rejection of %s", err, field)
}
})
}
}
func TestStorageS3CredentialEnvNamesLoadAndValidate(t *testing.T) {
t.Setenv("CUSTOM_KEY_ID", "synthetic-access-key")
t.Setenv("CUSTOM_SECRET", "synthetic-secret-key")
pipelineYAML := testPipelineBaseYAML + `
storage:
backend: s3
s3:
bucket: my-dnd-archive
access_key_id_env: CUSTOM_KEY_ID
secret_access_key_env: CUSTOM_SECRET
access_key_id: CUSTOM_KEY_ID
secret_access_key: CUSTOM_SECRET
`
pipelinePath, sessionPath := writeConfigFiles(t, pipelineYAML, testSessionBaseYAML)
@@ -97,55 +112,69 @@ storage:
if err != nil {
t.Fatalf("Load() error = %v", err)
}
if cfg.Pipeline.Storage.S3.AccessKeyIDEnv != "CUSTOM_KEY_ID" {
t.Fatalf("storage.s3.access_key_id_env = %q, want CUSTOM_KEY_ID", cfg.Pipeline.Storage.S3.AccessKeyIDEnv)
if cfg.Pipeline.Storage.S3.AccessKeyID == nil || cfg.Pipeline.Storage.S3.AccessKeyID.EnvName() != "CUSTOM_KEY_ID" || cfg.Pipeline.Storage.S3.AccessKeyID.Value() != "synthetic-access-key" {
t.Fatal("storage.s3.access_key_id did not retain its resolved typed reference")
}
if cfg.Pipeline.Storage.S3.SecretKeyEnv != "CUSTOM_SECRET" {
t.Fatalf("storage.s3.secret_access_key_env = %q, want CUSTOM_SECRET", cfg.Pipeline.Storage.S3.SecretKeyEnv)
if cfg.Pipeline.Storage.S3.SecretAccessKey == nil || cfg.Pipeline.Storage.S3.SecretAccessKey.EnvName() != "CUSTOM_SECRET" || cfg.Pipeline.Storage.S3.SecretAccessKey.Value() != "synthetic-secret-key" {
t.Fatal("storage.s3.secret_access_key did not retain its resolved typed reference")
}
if err := Validate(cfg); err != nil {
t.Fatalf("Validate() error = %v", err)
}
}
func TestStorageS3CredentialEnvValidation(t *testing.T) {
func TestStorageS3CredentialReferenceValidation(t *testing.T) {
tests := []struct {
name string
pipelineYML string
wantErr string
}{
{
name: "invalid access key env name",
name: "invalid access key reference",
pipelineYML: testPipelineBaseYAML + `
storage:
backend: s3
s3:
bucket: my-dnd-archive
access_key_id_env: "123BAD"
access_key_id: "123BAD"
secret_access_key: CUSTOM_SECRET
`,
wantErr: "pipeline.storage.s3.access_key_id_env must be a valid environment variable name",
wantErr: "secret.invalid_name",
},
{
name: "invalid secret key env name",
name: "partial pair",
pipelineYML: testPipelineBaseYAML + `
storage:
backend: s3
s3:
bucket: my-dnd-archive
secret_access_key_env: "bad-name"
access_key_id: CUSTOM_KEY_ID
`,
wantErr: "pipeline.storage.s3.secret_access_key_env must be a valid environment variable name",
wantErr: "must be configured together",
},
{
name: "missing effective reference",
pipelineYML: testPipelineBaseYAML + `
storage:
backend: s3
s3:
bucket: my-dnd-archive
access_key_id: MISSING_CUSTOM_KEY_ID
secret_access_key: MISSING_CUSTOM_SECRET
`,
wantErr: "secret.unset",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pipelinePath, sessionPath := writeConfigFiles(t, tt.pipelineYML, testSessionBaseYAML)
cfg, err := Load(pipelinePath, sessionPath)
if err != nil {
t.Fatalf("Load() error = %v", err)
pipelinePath, _ := writeConfigFiles(t, tt.pipelineYML, testSessionBaseYAML)
t.Setenv("CUSTOM_KEY_ID", "synthetic-access-key")
t.Setenv("CUSTOM_SECRET", "synthetic-secret-key")
cfg, err := LoadPipeline(pipelinePath)
if err == nil {
err = ValidatePipelineConfig(cfg)
}
err = Validate(cfg)
if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
t.Fatalf("Validate() error = %v, want to contain %q", err, tt.wantErr)
}