feat(iam): allow frontend tf-poc HCP apply destroy (PLAT-193)

This commit is contained in:
Adam Moussa 2026-09-11 17:37:51 -04:00
parent 3c54df6341
commit d4232ba045
No known key found for this signature in database
2 changed files with 97 additions and 22 deletions

View file

@ -457,9 +457,14 @@ job:
a false gate as rollback after CloudFormation owns a role.
The apply roles explicitly deny role lifecycle/trust/boundary/managed-policy
changes, `PassRole`, secret and parameter reads, CloudFront/S3 create and
delete (including OAC mutation), and deletion of inline role or bucket
policies. `UpdateDistribution` is allowed on the exact pinned distribution ARN.
changes, `PassRole`, secret and parameter reads, CloudFront/S3 create, and
(for dev and staging) CloudFront/S3 delete including OAC mutation and bucket
policy deletion. The tf-poc apply role is the exception: it keeps create
denied and allows destroy of the exact rehearsal bucket (including object
versions), distribution `E73KH1SPNFL00`, OAC `E14MP8Z5YRWO93`, function
`us-east-1shocfrontendtfpocSpaRewrite4B1A4F5F`, and A/AAAA in
`Z10433621DH3UOWM8663D`. It still does not delete `githubdeploy-*`; SCP
`ProtectDeploymentPrincipalLifecycle` denies that for HCP principals. `UpdateDistribution` is allowed on the exact pinned distribution ARN.
`CreateInvalidation` and `GetInvalidation` are allowed on that same ARN so the
Terraform invalidation action can run. `GetObject`/`GetObjectTagging`/`PutObject`/`PutObjectTagging` on
`.release/current` lets Terraform own the release pointer, including the

View file

@ -230,6 +230,93 @@ const frontendReadPolicy = (
};
};
const infrastructureReplacementDenyActions = (
environment: FrontendEnvironment,
): string[] => {
const createOnly = [
"cloudfront:CreateDistribution",
"cloudfront:CreateFunction",
"cloudfront:CreateOriginAccessControl",
"s3:CreateBucket",
];
if (environment.key === "tf-poc") {
// Destroy needs Delete* and the Put/Delete calls Terraform uses to
// remove versioning, encryption, ownership, and public-access configs.
return createOnly;
}
return [
...createOnly,
"cloudfront:DeleteDistribution",
"cloudfront:DeleteFunction",
"cloudfront:DeleteOriginAccessControl",
"cloudfront:UpdateOriginAccessControl",
"s3:DeleteBucket",
"s3:DeleteBucketEncryption",
"s3:DeleteBucketOwnershipControls",
"s3:DeleteBucketPolicy",
"s3:DeleteBucketPublicAccessBlock",
"s3:PutBucketOwnershipControls",
"s3:PutBucketPublicAccessBlock",
"s3:PutBucketVersioning",
"s3:PutEncryptionConfiguration",
];
};
const frontendPocDestroyStatements = (
environment: FrontendEnvironment,
): Record<string, unknown>[] => {
if (environment.key !== "tf-poc") {
return [];
}
const siteBucketArn = bucketArn(environment.bucketName);
return [
{
Sid: "DestroyExactPocBucket",
Effect: "Allow",
Action: [
"s3:DeleteBucket",
"s3:DeleteBucketEncryption",
"s3:DeleteBucketOwnershipControls",
"s3:DeleteBucketPolicy",
"s3:DeleteBucketPublicAccessBlock",
"s3:GetBucketVersioning",
"s3:ListBucket",
"s3:ListBucketVersions",
"s3:PutBucketOwnershipControls",
"s3:PutBucketPublicAccessBlock",
"s3:PutBucketVersioning",
"s3:PutEncryptionConfiguration",
],
Resource: siteBucketArn,
},
{
Sid: "DestroyExactPocBucketObjects",
Effect: "Allow",
Action: [
"s3:DeleteObject",
"s3:DeleteObjectVersion",
"s3:GetObject",
"s3:GetObjectVersion",
],
Resource: `${siteBucketArn}/*`,
},
{
Sid: "DestroyExactPocCloudFront",
Effect: "Allow",
Action: [
"cloudfront:DeleteDistribution",
"cloudfront:DeleteFunction",
"cloudfront:DeleteOriginAccessControl",
],
Resource: [
distributionArn(environment.distributionId),
functionArn(environment.functionName),
originAccessControlArn(environment.originAccessControlId),
],
},
];
};
const frontendApplyPolicy = (
environment: FrontendEnvironment,
): Record<string, unknown> => ({
@ -269,25 +356,7 @@ const frontendApplyPolicy = (
{
Sid: "DenyInfrastructureReplacement",
Effect: "Deny",
Action: [
"cloudfront:CreateDistribution",
"cloudfront:CreateFunction",
"cloudfront:CreateOriginAccessControl",
"cloudfront:DeleteDistribution",
"cloudfront:DeleteFunction",
"cloudfront:DeleteOriginAccessControl",
"cloudfront:UpdateOriginAccessControl",
"s3:CreateBucket",
"s3:DeleteBucket",
"s3:DeleteBucketEncryption",
"s3:DeleteBucketOwnershipControls",
"s3:DeleteBucketPolicy",
"s3:DeleteBucketPublicAccessBlock",
"s3:PutBucketOwnershipControls",
"s3:PutBucketPublicAccessBlock",
"s3:PutBucketVersioning",
"s3:PutEncryptionConfiguration",
],
Action: infrastructureReplacementDenyActions(environment),
Resource: "*",
},
{
@ -403,6 +472,7 @@ const frontendApplyPolicy = (
},
},
},
...frontendPocDestroyStatements(environment),
],
});