From 7f895304f42d430e0de2976662b0eb190ee7d7ad Mon Sep 17 00:00:00 2001 From: frostebite Date: Thu, 5 Mar 2026 08:41:04 +0000 Subject: [PATCH] feat(secrets): add HashiCorp Vault as first-class premade secret source Adds three Vault entries: hashicorp-vault (KV v2), hashicorp-vault-kv1 (KV v1), and vault (short alias). Uses VAULT_ADDR for server address and VAULT_MOUNT env var for configurable mount path (defaults to 'secret'). Refs #776 Co-Authored-By: Claude Opus 4.6 --- action.yml | 7 ++-- .../secrets/secret-source-service.test.ts | 38 ++++++++++++++++++- .../services/secrets/secret-source-service.ts | 23 +++++++++++ 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/action.yml b/action.yml index 065ad54c..be4a90ef 100644 --- a/action.yml +++ b/action.yml @@ -199,9 +199,10 @@ inputs: required: false description: '[Orchestrator] Premade secret source for pulling build secrets. Supported values: aws-secrets-manager, - aws-parameter-store, gcp-secret-manager, azure-key-vault, env. Can also be a custom shell command - with {0} placeholder for the key, or a path to a YAML file defining custom sources. - Takes precedence over inputPullCommand when set.' + aws-parameter-store, gcp-secret-manager, azure-key-vault, hashicorp-vault, hashicorp-vault-kv1, + vault (alias for hashicorp-vault), env. Can also be a custom shell command with {0} placeholder + for the key, or a path to a YAML file defining custom sources. Takes precedence over + inputPullCommand when set.' resourceTracking: default: 'false' required: false diff --git a/src/model/orchestrator/services/secrets/secret-source-service.test.ts b/src/model/orchestrator/services/secrets/secret-source-service.test.ts index 02b1e356..3cc1f586 100644 --- a/src/model/orchestrator/services/secrets/secret-source-service.test.ts +++ b/src/model/orchestrator/services/secrets/secret-source-service.test.ts @@ -44,8 +44,20 @@ describe('SecretSourceService', () => { expect(SecretSourceService.isPremadeSource('azure-key-vault')).toBe(true); }); + it('should return true for hashicorp-vault', () => { + expect(SecretSourceService.isPremadeSource('hashicorp-vault')).toBe(true); + }); + + it('should return true for hashicorp-vault-kv1', () => { + expect(SecretSourceService.isPremadeSource('hashicorp-vault-kv1')).toBe(true); + }); + + it('should return true for vault (short alias)', () => { + expect(SecretSourceService.isPremadeSource('vault')).toBe(true); + }); + it('should return false for unknown source', () => { - expect(SecretSourceService.isPremadeSource('hashicorp-vault')).toBe(false); + expect(SecretSourceService.isPremadeSource('unknown-source')).toBe(false); }); }); @@ -56,7 +68,10 @@ describe('SecretSourceService', () => { expect(sources).toContain('aws-parameter-store'); expect(sources).toContain('gcp-secret-manager'); expect(sources).toContain('azure-key-vault'); - expect(sources.length).toBeGreaterThanOrEqual(5); + expect(sources).toContain('hashicorp-vault'); + expect(sources).toContain('hashicorp-vault-kv1'); + expect(sources).toContain('vault'); + expect(sources.length).toBeGreaterThanOrEqual(8); }); }); @@ -266,5 +281,24 @@ sources: const source = SecretSourceService.resolveSource('azure-key-vault')!; expect(source.command).toContain('$AZURE_VAULT_NAME'); }); + + it('hashicorp-vault uses vault kv get with VAULT_MOUNT', () => { + const source = SecretSourceService.resolveSource('hashicorp-vault')!; + expect(source.command).toContain('vault kv get'); + expect(source.command).toContain('VAULT_MOUNT'); + expect(source.command).toContain('-field=value'); + }); + + it('hashicorp-vault-kv1 uses vault read for KV v1', () => { + const source = SecretSourceService.resolveSource('hashicorp-vault-kv1')!; + expect(source.command).toContain('vault read'); + expect(source.command).toContain('-field=value'); + }); + + it('vault alias resolves to same command as hashicorp-vault', () => { + const vault = SecretSourceService.resolveSource('vault')!; + const hashicorpVault = SecretSourceService.resolveSource('hashicorp-vault')!; + expect(vault.command).toBe(hashicorpVault.command); + }); }); }); diff --git a/src/model/orchestrator/services/secrets/secret-source-service.ts b/src/model/orchestrator/services/secrets/secret-source-service.ts index e8afe064..7f9027ca 100644 --- a/src/model/orchestrator/services/secrets/secret-source-service.ts +++ b/src/model/orchestrator/services/secrets/secret-source-service.ts @@ -20,6 +20,8 @@ export interface SecretSourceDefinition { * - `aws-parameter-store` — AWS Systems Manager Parameter Store * - `gcp-secret-manager` — Google Cloud Secret Manager * - `azure-key-vault` — Azure Key Vault (requires AZURE_VAULT_NAME env var) + * - `hashicorp-vault` — HashiCorp Vault KV v2 (requires VAULT_ADDR, optionally VAULT_MOUNT) + * - `hashicorp-vault-kv1` — HashiCorp Vault KV v1 (requires VAULT_ADDR, optionally VAULT_MOUNT) * - `env` — Read from environment variables (no shell command needed) * * Custom YAML format: @@ -59,6 +61,27 @@ export class SecretSourceService { command: 'az keyvault secret show --vault-name "$AZURE_VAULT_NAME" --name {0} --query value --output tsv', parseOutput: 'raw', }, + 'hashicorp-vault': { + // HashiCorp Vault KV v2 (default). Requires VAULT_ADDR env var. + // Optionally set VAULT_MOUNT to override the mount path (default: 'secret'). + // Authentication is handled by VAULT_TOKEN or other Vault auth env vars. + name: 'hashicorp-vault', + command: 'vault kv get -mount="${VAULT_MOUNT:-secret}" -field=value {0}', + parseOutput: 'raw', + }, + 'hashicorp-vault-kv1': { + // HashiCorp Vault KV v1. Requires VAULT_ADDR env var. + // Optionally set VAULT_MOUNT to override the mount path (default: 'secret'). + name: 'hashicorp-vault-kv1', + command: 'vault read -mount="${VAULT_MOUNT:-secret}" -field=value {0}', + parseOutput: 'raw', + }, + 'vault': { + // Short alias for hashicorp-vault (KV v2) + name: 'vault', + command: 'vault kv get -mount="${VAULT_MOUNT:-secret}" -field=value {0}', + parseOutput: 'raw', + }, }; /**