Skip to content

Commit 7e152fb

Browse files
authored
fix(aws): allow the scoped SAM transform (#185)
## Summary - let the CloudFormation service role call `cloudformation:CreateChangeSet` only on the regional AWS-owned `Serverless-2016-10-31` transform - keep arbitrary CloudFormation stacks and change sets outside the service role - add policy-conformance coverage and document the trust boundary ## Evidence A real least-privilege deployment from `HayaSendDeploymentOperatorTest` reached SAM change-set creation but CloudFormation rejected its own service-role session on the transform ARN. AWS documents this exact resource-level permission pattern: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/control-access-with-iam.html The failed stack remained `REVIEW_IN_PROGRESS` with no application resources. It was deleted through the restricted operator role, and all seven exact-prefix artifact versions were purged; the stack and prefix are absent. ## Validation - `npm run check` - `npm test -- --run` — 629 passed, 6 skipped - SAM lint passed - live CloudFormation template validation passed in account `330599756148`, `ap-northeast-1` - GPG-signed commit `aab278aa9d822dfc7eb6f2666551f6d34b95d4ee` This advances #174 but does not mark its least-privilege or lifecycle proof complete until an exact-main rerun succeeds.
1 parent 3b97e53 commit 7e152fb

4 files changed

Lines changed: 25 additions & 6 deletions

File tree

deploy/aws-cloudformation-bootstrap.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ Resources:
9999
PolicyDocument:
100100
Version: "2012-10-17"
101101
Statement:
102+
- Sid: UseOnlyTheSAMTransform
103+
Effect: Allow
104+
Action: cloudformation:CreateChangeSet
105+
Resource: !Sub "arn:${AWS::Partition}:cloudformation:${AWS::Region}:aws:transform/Serverless-2016-10-31"
102106
- Effect: Allow
103107
Action:
104108
- iam:AttachRolePolicy

docs/aws-quickstart.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ policy. The operator policy can manage only HayaSend stack names under the
3939
selected prefix, that artifact path, and `iam:PassRole` for the exact service
4040
role. It does not grant the operator Lambda, DynamoDB, SES, backup, or other
4141
application data-plane provisioning permissions.
42+
The service role may invoke the regional AWS-owned SAM transform, but it
43+
cannot create arbitrary CloudFormation stacks or change sets.
4244

4345
Apply from a one-time IAM bootstrap session only after reviewing the template:
4446

docs/operations.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,15 @@ retained-resource and stack-deletion controls.
6262
The checked-in bootstrap policy is action-scoped to the AWS services generated
6363
by `template.yaml`; it is not AdministratorAccess. IAM role creation and
6464
passing are restricted to the application stack prefix and approved AWS
65-
services. Some other CloudFormation service actions still require
66-
`Resource: "*"`, especially create-time Lambda, API Gateway, backup, KMS, and
67-
SES operations whose final ARN does not yet exist. Treat the optional
68-
organizational permissions boundary and SCPs as the outer guardrail. CI
69-
rejects a newly introduced application resource type until its required
70-
service prefix is reviewed in the bootstrap-policy conformance test.
65+
services. The service role may call `cloudformation:CreateChangeSet` only on
66+
the regional AWS-owned `Serverless-2016-10-31` transform required to expand
67+
the SAM template; it cannot create arbitrary CloudFormation stacks or change
68+
sets. Some other CloudFormation service actions still require `Resource: "*"`,
69+
especially create-time Lambda, API Gateway, backup, KMS, and SES operations
70+
whose final ARN does not yet exist. Treat the optional organizational
71+
permissions boundary and SCPs as the outer guardrail. CI rejects a newly
72+
introduced application resource type until its required service prefix is
73+
reviewed in the bootstrap-policy conformance test.
7174

7275
`cleanup aws --apply` needs `cloudformation:DeleteStack` and, for the normal
7376
protected stack, `cloudformation:UpdateTerminationProtection` in addition to

tests/aws-cloudformation-bootstrap-template.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,16 @@ describe("AWS CloudFormation deployment bootstrap template", () => {
101101
bootstrap.indexOf(" CloudFormationServiceRole:"),
102102
bootstrap.indexOf("\n OperatorPolicy:"),
103103
);
104+
expect(serviceRoleSection).toContain("Sid: UseOnlyTheSAMTransform");
105+
expect(serviceRoleSection).toContain(
106+
"Action: cloudformation:CreateChangeSet",
107+
);
108+
expect(serviceRoleSection).toContain(
109+
'Resource: !Sub "arn:${AWS::Partition}:cloudformation:${AWS::Region}:aws:transform/Serverless-2016-10-31"',
110+
);
111+
expect(serviceRoleSection).not.toContain(
112+
"arn:${AWS::Partition}:cloudformation:${AWS::Region}:aws:transform/*",
113+
);
104114
expect(serviceRoleSection).toContain(
105115
'Resource: !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${ApplicationStackNamePrefix}*"',
106116
);

0 commit comments

Comments
 (0)