mirror of
https://github.com/Sea-Haven-Industries/seahaven-org-baseline.git
synced 2026-09-30 04:33:15 +00:00
88 lines
3.6 KiB
TypeScript
88 lines
3.6 KiB
TypeScript
|
|
import * as cdk from "aws-cdk-lib";
|
||
|
|
import * as kms from "aws-cdk-lib/aws-kms";
|
||
|
|
import * as iam from "aws-cdk-lib/aws-iam";
|
||
|
|
import * as backup from "aws-cdk-lib/aws-backup";
|
||
|
|
import { Construct } from "constructs";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Offsite AWS Backup vault for Sea Haven (account 328440206208), in us-west-2.
|
||
|
|
*
|
||
|
|
* This is the Copy3 / offsite leg of the 3-2-1 strategy and the only immutable
|
||
|
|
* recovery path in the account. The primary plan (see backup-stack.ts, us-east-1)
|
||
|
|
* copies recovery points here cross-region. Closes audit finding C-7 together
|
||
|
|
* with backup-stack.
|
||
|
|
*
|
||
|
|
* Vault Lock is GOVERNANCE mode for now (minRetention only, no `changeableFor`):
|
||
|
|
* recovery points cannot be silently deleted, but a principal with explicit
|
||
|
|
* `backup:DeleteRecoveryPoint` / `backup:DeleteBackupVaultLockConfiguration`
|
||
|
|
* permission can still intervene while we validate the plan. Graduate to
|
||
|
|
* COMPLIANCE mode later by adding `changeableFor` (irreversible after the
|
||
|
|
* cooling-off window) — a one-line change + redeploy.
|
||
|
|
*/
|
||
|
|
export class BackupOffsiteStack extends cdk.Stack {
|
||
|
|
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
||
|
|
super(scope, id, props);
|
||
|
|
|
||
|
|
// CMK encrypting offsite recovery points (rotation on; RETAIN so a stack
|
||
|
|
// teardown never strands/destroys the only immutable copy).
|
||
|
|
const vaultKey = new kms.Key(this, "OffsiteVaultKey", {
|
||
|
|
alias: "backup-offsite-vault",
|
||
|
|
description: "Encrypts offsite AWS Backup recovery points (us-west-2)",
|
||
|
|
enableKeyRotation: true,
|
||
|
|
removalPolicy: cdk.RemovalPolicy.RETAIN,
|
||
|
|
});
|
||
|
|
|
||
|
|
// The L2 BackupVault does NOT grant the backup service use of a customer
|
||
|
|
// CMK — without this, cross-region COPY jobs of encrypted RDS/EBS recovery
|
||
|
|
// points fail (and silently, with no CloudTrail). Grant backup.amazonaws.com
|
||
|
|
// the minimum KMS actions on this destination key, incl. CreateGrant.
|
||
|
|
vaultKey.addToResourcePolicy(
|
||
|
|
new iam.PolicyStatement({
|
||
|
|
sid: "AllowAwsBackupUseOfTheKey",
|
||
|
|
principals: [new iam.ServicePrincipal("backup.amazonaws.com")],
|
||
|
|
// Action set matches AWS's documented Backup vault-key policy; scoped
|
||
|
|
// to this account so only this account's Backup service can use it.
|
||
|
|
actions: [
|
||
|
|
"kms:Decrypt",
|
||
|
|
"kms:GenerateDataKey",
|
||
|
|
"kms:GenerateDataKeyWithoutPlaintext",
|
||
|
|
"kms:ReEncrypt*",
|
||
|
|
"kms:DescribeKey",
|
||
|
|
],
|
||
|
|
resources: ["*"],
|
||
|
|
conditions: { StringEquals: { "aws:SourceAccount": this.account } },
|
||
|
|
})
|
||
|
|
);
|
||
|
|
vaultKey.addToResourcePolicy(
|
||
|
|
new iam.PolicyStatement({
|
||
|
|
sid: "AllowAwsBackupCreateGrant",
|
||
|
|
principals: [new iam.ServicePrincipal("backup.amazonaws.com")],
|
||
|
|
actions: ["kms:CreateGrant"],
|
||
|
|
resources: ["*"],
|
||
|
|
conditions: {
|
||
|
|
Bool: { "kms:GrantIsForAWSResource": "true" },
|
||
|
|
StringEquals: { "aws:SourceAccount": this.account },
|
||
|
|
},
|
||
|
|
})
|
||
|
|
);
|
||
|
|
|
||
|
|
new backup.BackupVault(this, "OffsiteVault", {
|
||
|
|
backupVaultName: "seahaven-offsite",
|
||
|
|
encryptionKey: vaultKey,
|
||
|
|
removalPolicy: cdk.RemovalPolicy.RETAIN,
|
||
|
|
// Governance-mode Vault Lock: no `changeableFor`, so it stays adjustable.
|
||
|
|
lockConfiguration: {
|
||
|
|
minRetention: cdk.Duration.days(30),
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
cdk.Tags.of(this).add("Project", "account-baseline");
|
||
|
|
cdk.Tags.of(this).add("Owner", "adam@seahavenind.com");
|
||
|
|
cdk.Tags.of(this).add("Environment", "prod");
|
||
|
|
cdk.Tags.of(this).add("ManagedBy", "cdk");
|
||
|
|
|
||
|
|
new cdk.CfnOutput(this, "OffsiteVaultName", { value: "seahaven-offsite" });
|
||
|
|
new cdk.CfnOutput(this, "OffsiteVaultKmsKeyArn", { value: vaultKey.keyArn });
|
||
|
|
}
|
||
|
|
}
|