mirror of
https://github.com/Sea-Haven-Industries/seahaven-org-baseline.git
synced 2026-09-30 08:03:19 +00:00
[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:
parent
e9a184cf96
commit
5002ed86d8
1 changed files with 63 additions and 7 deletions
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue