feat(credentials): mirror backup creds + age key into Vault (CONTRACT_002)
foundation/backup/backup-credentials was never populated in Vault. Add a writer (same ADR-007 docker-exec-over-SSH pattern, GATE A / dependsOn vault.init) that mirrors the config-seeded offsite S3 creds and the age key into Vault, completing CONTRACT_002 §2.3 for in-Vault consumers (Layer-1 ESO, the weekly backup-verify job). - config.ts: loadBackupSecrets() — single reader of the backup secret slice (offsite creds + age recipient/identity), keeping components off raw Config. - credentials.ts: writeBackupCredentialsToVault() — idempotent vault kv put; secret values on stdin (D2), non-secrets as shell vars. - index.ts: wire it beside the data-plane creds writer. Keys written: offsiteEndpoint, offsiteAccessKey, offsiteSecretKey, backupAgeRecipient, backupAgeIdentity. Validated live: +1 resource, then 42 unchanged (idempotent); vault kv get shows all five keys populated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
92e8f978a5
commit
f2ef9bc922
3 changed files with 95 additions and 1 deletions
|
|
@ -88,6 +88,33 @@ export interface FoundationConfig {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The backup secret slice (CONTRACT_001 §1.3 + CONTRACT_002 `foundation/backup/
|
||||
* backup-credentials`). Kept OUT of the non-secret FoundationConfig surface: these
|
||||
* are `secure:`-encrypted (offsite creds, age identity) or a public key that only
|
||||
* the Vault mirror + backup scripts consume. `loadBackupSecrets()` is the single
|
||||
* reader (composition point), so components still never touch raw pulumi.Config.
|
||||
*/
|
||||
export interface BackupSecrets {
|
||||
offsiteEndpoint: string; // non-secret (also in FoundationConfig.backup)
|
||||
offsiteAccessKey: pulumi.Output<string>;
|
||||
offsiteSecretKey: pulumi.Output<string>;
|
||||
ageRecipient: string; // age1… public key (non-secret)
|
||||
ageIdentity: pulumi.Output<string>; // AGE-SECRET-KEY-… (secret; survives Vault loss via config)
|
||||
}
|
||||
|
||||
/** Reads the backup secret slice for the Vault mirror (CONTRACT_002 §2.3). */
|
||||
export function loadBackupSecrets(): BackupSecrets {
|
||||
const c = new pulumi.Config("foundation");
|
||||
return {
|
||||
offsiteEndpoint: c.require("backup.offsiteEndpoint"),
|
||||
offsiteAccessKey: c.requireSecret("backup.offsiteAccessKey"),
|
||||
offsiteSecretKey: c.requireSecret("backup.offsiteSecretKey"),
|
||||
ageRecipient: c.require("backup.ageRecipient"),
|
||||
ageIdentity: c.requireSecret("backup.ageIdentity"),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The SSH private key path is supplied by ENV (CONTRACT_001 §1: `SSH_PRIVATE_KEY_PATH`,
|
||||
* default ~/.ssh/id_rsa) — NEVER by Pulumi config. Exposed separately from
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue