[INFRA-94] Add Backup vault access policy on seahaven-primary (#19)

Reintroduce the scoped vault access policy that was split out of INFRA-89
after two lockout-class bugs. Adds a Deny on the destructive recovery-point
and vault-lifecycle actions (DeleteRecoveryPoint, UpdateRecoveryPointLifecycle,
DeleteBackupVault, DeleteBackupVaultAccessPolicy,
DeleteBackupVaultLockConfiguration, PutBackupVaultLockConfiguration) for every
principal except three exempted operational identities via StringNotLike on
aws:PrincipalArn:

  1. SSO AdministratorAccess role (break-glass human admin)
  2. seahaven-backup-service-role (AWS Backup lifecycle)
  3. cdk-hnb659fds-cfn-exec-role-* (CloudFormation manages the vault)

The CFN-exec-role exemption is the fix for the 2026-06-08 strand failure: without
it CloudFormation cannot re-assert the vault lock config and the deploy strands
the policy. Uses Deny + AnyPrincipal + StringNotLike (not NotPrincipal, which
rejects wildcard ARNs). aws:PrincipalArn normalizes assumed-role sessions to the
IAM role ARN, so the iam::role/ ARN forms are correct (AWS docs: "Do not specify
the assumed role session ARN as a value for this condition key").

Deployed and verified: deploy succeeded (proves exec role not locked out),
access policy present with all three exemptions, vault still Locked
(min1/max2555, LockDate null, 168 RPs), follow-up cdk diff clean (no drift).
This commit is contained in:
Adam Moussa 2026-06-08 17:34:01 -04:00 • committed by GitHub
parent e9a184cf96
commit 5002ed86d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -87,13 +87,16 @@ export class BackupStack extends cdk.Stack {
// is written to MATCH the live lock exactly, so the deploy diff is a no-op
// adoption — it does not change the live vault.
//
// NOTE: the scoped vault access policy that previously lived here was DROPPED
// from this deploy (INFRA-94). Attaching a BackupVaultAccessPolicy that
// denies DeleteBackupVault / PutBackupVaultLockConfiguration to all but a
// break-glass principal made the vault unmanageable by CloudFormation/CDK.
// The deny-manual-deletion control is tracked separately in INFRA-94 and
// will be reintroduced via a safe mechanism. Governance lock codify +
// backup selections land here.
// NOTE: the scoped vault access policy is (re)introduced below (INFRA-94)
// with the fix for the two lockout-class bugs that got it split out of
// INFRA-89: the deny statement now exempts THREE principals via
// StringNotLike on aws:PrincipalArn — the SSO AdministratorAccess role (break
// glass), the backup service role, AND the CDK CFN execution role. The
// CFN-exec-role exemption is MANDATORY: without it CloudFormation cannot
// re-assert the vault lock config / manage the vault and the deploy strands
// the policy (this happened 2026-06-08). NotPrincipal is deliberately NOT
// used (it rejects wildcard ARNs). Governance lock codify + backup
// selections land here.
const primaryVault = new backup.BackupVault(this, "PrimaryVault", {
backupVaultName: "seahaven-primary",
encryptionKey: vaultKey,
@ -105,6 +108,59 @@ export class BackupStack extends cdk.Stack {
},
});
// Vault access policy (INFRA-94): Deny the destructive recovery-point and
// vault-lifecycle actions to EVERY principal EXCEPT the three operational
// identities below. This is defense-in-depth on top of the GOVERNANCE lock —
// it blocks manual deletion / lifecycle tampering even from accounts that
// hold the equivalent IAM permissions.
//
// Deny (not Allow): a resource-policy Deny overrides any identity-based
// Allow, which is exactly what we want for a guardrail. The StringNotLike
// condition means "this Deny applies UNLESS the caller's ARN matches one of
// the exempted patterns" — i.e. the three exempt principals are NOT denied.
//
// Exemptions (all THREE required):
// 1. SSO AdministratorAccess role — break-glass human admin path. Matched by
// wildcard because the AWSReservedSSO role name carries a permission-set
// hash suffix.
// 2. seahaven-backup-service-role — AWS Backup uses it for lifecycle
// expiry of recovery points; denying it would break the plan's
// deleteAfter cleanup.
// 3. cdk-hnb659fds-cfn-exec-role — the CloudFormation execution role. CFN
// re-asserts the vault lock config and manages the vault on every deploy;
// omitting it strands the policy and fails the deploy (INFRA-94,
// 2026-06-08). Wildcard-suffixed to cover the region-qualified name.
//
// NotPrincipal is intentionally avoided — it does not accept wildcard ARNs.
primaryVault.addToAccessPolicy(
new iam.PolicyStatement({
sid: "DenyDestructiveActionsExceptOperationalRoles",
effect: iam.Effect.DENY,
principals: [new iam.AnyPrincipal()],
actions: [
"backup:DeleteRecoveryPoint",
"backup:UpdateRecoveryPointLifecycle",
"backup:DeleteBackupVault",
"backup:DeleteBackupVaultAccessPolicy",
"backup:DeleteBackupVaultLockConfiguration",
"backup:PutBackupVaultLockConfiguration",
],
resources: ["*"],
conditions: {
StringNotLike: {
"aws:PrincipalArn": [
// 1. SSO AdministratorAccess (break-glass human admin)
`arn:aws:iam::${this.account}:role/aws-reserved/sso.amazonaws.com/*AWSReservedSSO_AdministratorAccess*`,
// 2. AWS Backup service role (lifecycle expiry of recovery points)
`arn:aws:iam::${this.account}:role/seahaven-backup-service-role`,
// 3. CDK CloudFormation execution role (MANDATORY — manages vault)
`arn:aws:iam::${this.account}:role/cdk-hnb659fds-cfn-exec-role-*`,
],
},
},
})
);
// Cross-region copy destination, referenced by literal ARN (the offsite
// stack is in another region; a literal ARN avoids crossRegionReferences /
// SSM exports). Stack ordering is enforced via addDependency in bin/app.ts.