Add secrets directory credential resolver

This commit is contained in:
2026-05-31 17:00:47 +00:00
parent 84f77ec0d0
commit 052aa8a64a
14 changed files with 665 additions and 6 deletions

View File

@@ -23,10 +23,15 @@ const (
)
type backendFactory struct {
registry *storage.Registry
registry *storage.Registry
environment config.Environment
}
func newBackendFactory() *backendFactory {
return newBackendFactoryWithEnvironment(config.ProcessEnvironment())
}
func newBackendFactoryWithEnvironment(environment config.Environment) *backendFactory {
registry := storage.NewRegistry()
_ = registry.Register(config.BackendLocal, func(ctx context.Context, cfg storage.OpenConfig) (storage.Backend, error) {
if err := ctx.Err(); err != nil {
@@ -49,7 +54,7 @@ func newBackendFactory() *backendFactory {
HostKeyPolicy: sshadapter.HostKeyPolicy(cfg[sshHostKeyPolicyKey]),
})
})
return &backendFactory{registry: registry}
return &backendFactory{registry: registry, environment: environment}
}
func (f *backendFactory) openSource(ctx context.Context, source config.Backend) (storage.Backend, error) {
@@ -70,6 +75,10 @@ func (f *backendFactory) openLocalPath(ctx context.Context, path string) (storag
return f.registry.Open(ctx, config.BackendLocal, storage.OpenConfig{storagePathKey: path})
}
func (f *backendFactory) resolveCredentials(creds config.Credentials) (config.ResolvedCredentials, error) {
return f.environment.ResolveCredentials(creds)
}
func sourceOpenConfig(source config.Backend) storage.OpenConfig {
cfg := storage.OpenConfig{storagePathKey: source.Path}
if source.Backend == config.BackendSSH {

View File

@@ -131,6 +131,26 @@ func TestBackendFactoryRejectsUnsupportedDestination(t *testing.T) {
}
}
func TestBackendFactoryResolvesCredentialsThroughEnvironment(t *testing.T) {
factory := newBackendFactoryWithEnvironment(config.NewEnvironment(map[string]string{
"ACCESS_KEY_ID": "secret-access",
"SECRET_ACCESS_KEY": "secret-secret",
}, func(string) (string, bool) {
return "", false
}))
creds, err := factory.resolveCredentials(config.Credentials{
AccessKeyIDEnv: "ACCESS_KEY_ID",
SecretAccessKeyEnv: "SECRET_ACCESS_KEY",
})
if err != nil {
t.Fatalf("resolveCredentials() error = %v", err)
}
if creds.AccessKeyID != "secret-access" || creds.SecretAccessKey != "secret-secret" {
t.Fatalf("resolved credentials = %#v", creds)
}
}
func TestBackendFactoryBuildsSSHSourceOpenConfig(t *testing.T) {
cfg := sourceOpenConfig(config.Backend{
Backend: config.BackendSSH,

View File

@@ -44,7 +44,16 @@ func runConfig(ctx context.Context, cfg config.Config, options RunOptions) error
}
summary := runSummary{dryRun: options.DryRun}
var failures runFailures
backends := newBackendFactory()
secretLoad, err := config.LoadSecretEnvironment(cfg.Secrets.Directory, nil)
if err != nil {
return err
}
if options.Stdout != nil {
if err := writeSecretConflictWarnings(options.Stdout, secretLoad.Conflicts); err != nil {
return err
}
}
backends := newBackendFactoryWithEnvironment(secretLoad.Environment)
transforms := newTransformRegistry()
if options.Stdout != nil {
if _, err := fmt.Fprintf(options.Stdout, "Configured pipelines: %d\n", len(cfg.Pipelines)); err != nil {
@@ -205,6 +214,15 @@ func destinationSummary(destinations []config.Destination) string {
return strings.Join(ids, ",")
}
func writeSecretConflictWarnings(w io.Writer, conflicts []config.SecretConflict) error {
for _, conflict := range conflicts {
if _, err := fmt.Fprintf(w, "Warning: secret %s ignored because the real environment already has that variable\n", conflict.Name); err != nil {
return err
}
}
return nil
}
func writeSSHWarnings(w io.Writer, pipeline config.Pipeline) error {
if pipeline.Source.Backend == config.BackendSSH && pipeline.Source.SSH.HostKeyPolicy == config.HostKeyPolicyOff {
if _, err := fmt.Fprintf(w, "Warning: pipeline=%s source host_key_policy=off disables SSH host key checking\n", pipeline.ID); err != nil {

View File

@@ -47,6 +47,77 @@ func TestRunDryRunPrintsConfigSummary(t *testing.T) {
}
}
func TestRunLoadsSecretsBeforeOpeningBackends(t *testing.T) {
sourceRoot := filepath.Join(t.TempDir(), "missing-source")
destinationRoot := t.TempDir()
configPath := writeConfigFile(t, `
secrets:
directory: `+filepath.Join(t.TempDir(), "missing-secrets")+`
pipelines:
- id: reports
source:
backend: local
path: `+sourceRoot+`
destinations:
- id: archive
backend: local
path: `+destinationRoot+`
`)
err := Run(context.Background(), RunOptions{ConfigPath: configPath})
if err == nil {
t.Fatal("Run() error = nil, want secrets directory error")
}
if !strings.Contains(err.Error(), "load secrets directory") {
t.Fatalf("Run() error = %v, want secrets directory error", err)
}
if strings.Contains(err.Error(), "missing-source") {
t.Fatalf("Run() error = %v, opened source before loading secrets", err)
}
}
func TestRunPrintsSecretConflictWarningWithoutValues(t *testing.T) {
name := "DISTRIBUTOR_TEST_RUN_SECRET"
t.Setenv(name, "process-value")
sourceRoot := t.TempDir()
destinationRoot := t.TempDir()
secretsRoot := t.TempDir()
if err := os.WriteFile(filepath.Join(secretsRoot, name), []byte("secret-value\n"), 0o600); err != nil {
t.Fatalf("write secret: %v", err)
}
writeSourceBundle(t, sourceRoot, "", testBundleOptions{})
configPath := writeConfigFile(t, `
secrets:
directory: `+secretsRoot+`
pipelines:
- id: reports
source:
backend: local
path: `+sourceRoot+`
destinations:
- id: archive
backend: local
path: `+destinationRoot+`
`)
var stdout bytes.Buffer
err := Run(context.Background(), RunOptions{
ConfigPath: configPath,
DryRun: true,
Stdout: &stdout,
})
if err != nil {
t.Fatalf("Run() error = %v", err)
}
output := stdout.String()
if !strings.Contains(output, "secret "+name+" ignored because the real environment already has that variable") {
t.Fatalf("stdout = %q, want secret conflict warning", output)
}
if strings.Contains(output, "process-value") || strings.Contains(output, "secret-value") {
t.Fatalf("stdout exposed secret values: %q", output)
}
}
func TestWriteSSHWarningsReportsInsecureHostKeyPolicy(t *testing.T) {
var stdout bytes.Buffer
err := writeSSHWarnings(&stdout, config.Pipeline{