feat(hcptf): add hcptf-mta-sts apply and plan roles to seahaven-hcptf (PLAT-243) (#178)
Some checks are pending
Deploy / deploy-management (push) Waiting to run
Deploy / deploy-external-dev (push) Waiting to run
Deploy / deploy-security (push) Waiting to run
Deploy / deploy-dev (push) Waiting to run
Deploy / deploy-prod (push) Waiting to run

* feat(hcptf): add hcptf-mta-sts apply and plan roles to seahaven-hcptf

New MtaStsRoles construct nested in the prod seahaven-hcptf stack for the
mta-sts-prod HCP workspace. Fresh roles, plain create, Retain on every
resource. Trust is StringEquals on the exact workspace sub per run phase.

Managed policies at /tf-managed/:
- mta-sts-hcptf-iam: manage only githubdeploy-mta-sts and its boundary;
  CreateRole requires that boundary; DenySelfMutation on hcptf-*,
  githubdeploy-*, cdk, OrganizationAccountAccessRole, seahaven-*
- mta-sts-hcptf-services: S3 on mta-sts-prod-*, CloudFront, ACM scoped
  to Project=mta-sts, SSM /mta-sts/deploy/* and the WAF ACL parameter,
  GitHub OIDC provider read
- mta-sts-hcptf-plan: enumerated refresh reads beside ViewOnlyAccess

Outputs MtaStsApplyRoleArn and MtaStsPlanRoleArn. README lists mta-sts
with the other prod exec roles that live in this stack.

* fix(hcptf): scope mta-sts CreatePolicy and plan policy reads to the boundary ARN

CreateDeployBoundary now names the boundary ARN as its Resource instead of
"*", keeping the BoundaryFor request-tag condition as a second gate.

The plan sidecar's GetPolicy, GetPolicyVersion, ListPolicyVersions, and
ListPolicyTags are merged into one RefreshDeployBoundary statement on the
boundary ARN. The boundary is the only managed policy in Terraform state,
and ViewOnlyAccess does not carry GetPolicy or GetPolicyVersion.

* fix(hcptf): replace cloudfront:* in mta-sts services policy with tag-gated grants

CloudFrontManage granted cloudfront:* on every CloudFront resource in the
account. Split into:

- CloudFrontRead: the Get and ListTagsForResource calls Terraform makes
- CloudFrontCreateTagged: CreateDistribution and TagResource on the
  distribution ARN type, gated on request tag Project=mta-sts
- CloudFrontManageTagged: Update, Delete, Tag, Untag, and CreateInvalidation
  gated on resource tag Project=mta-sts
- CloudFrontOac: Create, Update, Delete on the origin-access-control ARN
  type; OACs do not support tags

A distribution another workspace owns cannot be mutated by this role. The
workspace provider must set Project=mta-sts in default_tags.

* fix(hcptf): close mta-sts TagResource bypass and trim ACM and plan reads

CloudFrontCreateTagged keeps cloudfront:TagResource, which
CreateDistributionWithTags requires before the distribution has tags, but
adds Null aws:ResourceTag/Project so it applies only to a distribution with
no Project tag yet. An existing distribution owned by another workspace can
no longer be re-tagged into CloudFrontManageTagged's scope.

ACM is trimmed to what aws_acm_certificate calls: RequestCertificate,
DescribeCertificate, ListTagsForCertificate, AddTagsToCertificate,
RemoveTagsFromCertificate, DeleteCertificate. GetCertificate,
RenewCertificate, and ListCertificates are dropped from both roles.

RefreshDeployRole reads only githubdeploy-mta-sts; the two CFN-owned exec
roles are not in Terraform state.

* fix(hcptf): give CloudFront create actions Resource "*" in mta-sts services policy

cloudfront:CreateDistribution and cloudfront:CreateOriginAccessControl have
no resource type in the service authorization reference and only match
Resource "*". Scoping them to the distribution and OAC ARN types would have
implicitly denied the first apply. TagResource at create time stays on the
distribution ARN type with the RequestTag and Null ResourceTag conditions,
and OAC update and delete stay on the OAC ARN type.

Verified with iam simulate-custom-policy: CreateDistribution with request
tag Project=mta-sts allowed; TagResource, UpdateDistribution, and
DeleteDistribution on a distribution tagged Project=seahaven-site denied.
This commit is contained in:
Adam Moussa 2026-10-05 18:42:33 +00:00 • committed by GitHub
parent f76b767dcb
commit a43ec1f0f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 582 additions and 9 deletions

View file

@ -34,7 +34,7 @@ are noted):
| `seahaven-view-access` | 328440206208 | us-east-1 | Identity Center group `view`. Permission set `View` is `ViewOnlyAccess` on all five accounts. No data-plane reads and no assume-role. Membership is outside this stack. |
| `seahaven-external-dev-baseline` | 396287094661 | us-east-1 | Member-account baseline: Config, GuardDuty, Security Hub (FSBP + CIS v3.0), Access Analyzer, flow logs, budget |
| `seahaven-terraform-substrate` | 011934824531, 710827005802 | us-east-1 | Removed from the CDK app and from CD (PLAT-147). Live stacks remain until `scripts/delete-terraform-substrate-prod-dev.sh`. The six imported prod pairs are already forgotten. |
| `seahaven-hcptf` | 011934824531, 710827005802 | us-east-1 | payments-dashboard HCP apply and plan roles, scoped policies, and the Lambda boundary in prod and dev. Prod also owns the imported `hcptf-seahaven-site` apply and plan roles (PLAT-225), with policies at `/tf-managed/`. Trust is pinned per workspace. On the dev and prod deploy jobs. Do not create the roles. |
| `seahaven-hcptf` | 011934824531, 710827005802 | us-east-1 | payments-dashboard HCP apply and plan roles, scoped policies, and the Lambda boundary in prod and dev. Prod also owns the imported `hcptf-seahaven-site` apply and plan roles (PLAT-225) and the created `hcptf-mta-sts` apply and plan roles (PLAT-243), with policies at `/tf-managed/`. Trust is pinned per workspace. On the dev and prod deploy jobs. Do not create the payments-dashboard or seahaven-site roles by hand. |
| `seahaven-terraform-substrate` | 396287094661 | us-east-1 | Staged manually for SHOC backend/frontend adoption; exact HCP roles and deploy boundaries referencing the existing OIDC provider. Stays (PLAT-148). |
| `seahaven-security-baseline` | 001520130573 | us-east-1 | Member-account baseline for the delegated security-admin account (same construct set) |
| `seahaven-dev-baseline` | 710827005802 | us-east-1 | Member-account baseline for internal dev/staging (org-managed detection — no local GuardDuty/SecurityHub) |
@ -239,14 +239,16 @@ Console / one-shot CLI owns only:
Do not manage prod/dev workload IAM (`hcptf-<stack>` pairs, Lambda exec
roles, `policy/tf-managed/<stack>` ceilings) in the console. Do not append
new prod/dev `hcptf-<stack>` pairs to this template. New prod/dev HCP stacks
do not need an org-baseline IAM PR. Prod HCP exec roles for
payments-dashboard and seahaven-site both live in `seahaven-hcptf`.
Dev `seahaven-hcptf` is payments-dashboard only. Both account copies
are imported and are on the deploy jobs. The payments deploy creates
new prod/dev `hcptf-<stack>` pairs to this template. Prod HCP exec roles for
payments-dashboard, seahaven-site, and mta-sts all live in `seahaven-hcptf`
(`lib/seahaven-hcptf-stack.ts`, `lib/seahaven-site-hcptf-stack.ts`,
`lib/mta-sts-hcptf-stack.ts`). A new prod HCP workspace adds a nested
construct there. Dev `seahaven-hcptf` is payments-dashboard only. Both
account copies are on the deploy jobs. The payments deploy creates
`payments-dashboard-hcptf-iam`, `payments-dashboard-hcptf-services`, and
`payments-dashboard-hcptf-plan`, and removes the inline policies. Do not
create the roles, the boundary, or the seahaven-site roles.
create the payments-dashboard roles, the boundary, or the seahaven-site
roles by hand; they are imported. The mta-sts roles are a plain create.
**External-dev IAM stays in this repo (PLAT-148).** SHOC backend/frontend HCP
roles, SHOC deploy/runtime boundaries, `shoc-frontend-resources.ts`, and

View file

@ -244,6 +244,7 @@ new AppWebAclStack(app, "app-web-acl-prod", {
// payments-dashboard HCP roles, scoped policies, and the Lambda boundary.
// Prod also includes the imported seahaven-site apply and plan roles
// (PLAT-225). A create fails. Import site with `-c hcptfSiteImport=true`.
// Prod also creates the mta-sts apply and plan roles (PLAT-243).
// Trust is pinned per workspace.
const hcptfPaymentsImport = contextBoolean("hcptfPaymentsImport");
const hcptfSiteImport = contextBoolean("hcptfSiteImport");
@ -274,6 +275,7 @@ new SeahavenHcptfStack(app, "seahaven-hcptf", {
importExisting: hcptfPaymentsImport,
includeSeahavenSite: true,
siteImportExisting: hcptfSiteImport,
includeMtaSts: true,
});
new SeahavenHcptfStack(app, "seahaven-hcptf-dev", {

563
lib/mta-sts-hcptf-stack.ts Normal file
View file

@ -0,0 +1,563 @@
import * as cdk from "aws-cdk-lib";
import * as iam from "aws-cdk-lib/aws-iam";
import { Construct } from "constructs";
/**
* Prod exec roles for the mta-sts HCP workspace (PLAT-243).
*
* Nested in the prod seahaven-hcptf stack beside SeahavenSiteRoles. These
* roles are new. Plain CloudFormation create, no import template.
*
* The workspace owns four S3 origin buckets named `mta-sts-prod-<slug>`,
* four CloudFront distributions with OACs, four exact-name ACM
* certificates tagged Project=mta-sts, the SSM deploy contract under
* `/mta-sts/deploy/`, and the GitHub content-deploy role
* `githubdeploy-mta-sts` with its boundary. Policy files are published by
* GitHub Actions, not Terraform, so no object-level grants beyond the
* bucket itself are needed here.
*
* CloudFront distribution and ACM writes are gated on Project=mta-sts
* request and resource tags. The workspace provider must set that tag in
* default_tags, or the first apply fails on CreateDistribution.
*
* Inline policies are managed policies at /tf-managed/. Do not rename
* the roles.
*/
export class MtaStsRoles extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id);
// Overrides the parent stack's Project=payments-dashboard tag.
cdk.Tags.of(this).add("Project", "mta-sts", { priority: 200 });
const account = cdk.Stack.of(this).account;
const deployRole = `arn:aws:iam::${account}:role/tf-managed/githubdeploy-mta-sts`;
const boundary = `arn:aws:iam::${account}:policy/tf-managed/mta-sts-githubdeploy-boundary`;
// One ARN covers the four buckets and their objects. `*` spans `/`, so a
// second `mta-sts-prod-*/*` entry is redundant (Access Analyzer flags it).
const buckets = ["arn:aws:s3:::mta-sts-prod-*"];
const deployParams = `arn:aws:ssm:us-east-1:${account}:parameter/mta-sts/deploy/*`;
const wafParam = `arn:aws:ssm:us-east-1:${account}:parameter/seahaven/waf/app-web-acl-arn`;
const githubOidc = `arn:aws:iam::${account}:oidc-provider/token.actions.githubusercontent.com`;
const hcpOidc = `arn:aws:iam::${account}:oidc-provider/app.terraform.io`;
const iamPolicy = managedPolicy(
this,
"IamPolicy",
"mta-sts-hcptf-iam",
scopedIamPolicy(account, deployRole, boundary),
);
const services = managedPolicy(
this,
"ServicesPolicy",
"mta-sts-hcptf-services",
servicesPolicy(account, buckets, deployParams, wafParam, githubOidc),
);
const planRefresh = managedPolicy(
this,
"PlanPolicy",
"mta-sts-hcptf-plan",
planPolicy(buckets, deployParams, wafParam, githubOidc, deployRole, boundary),
);
const apply = new iam.CfnRole(this, "ApplyRole", {
roleName: "hcptf-mta-sts",
maxSessionDuration: 3600,
assumeRolePolicyDocument: trust(hcpOidc, "apply"),
managedPolicyArns: [iamPolicy.ref, services.ref],
tags: roleTags(),
});
retain(apply);
const plan = new iam.CfnRole(this, "PlanRole", {
roleName: "hcptf-mta-sts-plan",
maxSessionDuration: 3600,
assumeRolePolicyDocument: trust(hcpOidc, "plan"),
managedPolicyArns: [
"arn:aws:iam::aws:policy/job-function/ViewOnlyAccess",
planRefresh.ref,
],
tags: roleTags(),
});
retain(plan);
const applyArn = new cdk.CfnOutput(this, "ApplyRoleArn", { value: apply.attrArn });
const planArn = new cdk.CfnOutput(this, "PlanRoleArn", { value: plan.attrArn });
applyArn.overrideLogicalId("MtaStsApplyRoleArn");
planArn.overrideLogicalId("MtaStsPlanRoleArn");
}
}
function managedPolicy(
scope: Construct,
id: string,
name: string,
policyDocument: object,
): iam.CfnManagedPolicy {
const policy = new iam.CfnManagedPolicy(scope, id, {
managedPolicyName: name,
path: "/tf-managed/",
policyDocument,
});
retain(policy);
return policy;
}
function retain(resource: cdk.CfnResource): void {
resource.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.RETAIN;
resource.cfnOptions.updateReplacePolicy = cdk.CfnDeletionPolicy.RETAIN;
}
function roleTags(): cdk.CfnTag[] {
return [
{ key: "Project", value: "mta-sts" },
{ key: "Owner", value: "adam@seahavenind.com" },
{ key: "ManagedBy", value: "cdk" },
];
}
function trust(providerArn: string, phase: "apply" | "plan"): iam.PolicyDocument {
return iam.PolicyDocument.fromJson({
Version: "2012-10-17",
Statement: [
{
Sid: phase === "apply" ? "HcpApply" : "HcpPlan",
Effect: "Allow",
Action: "sts:AssumeRoleWithWebIdentity",
Principal: { Federated: providerArn },
Condition: {
StringEquals: {
"app.terraform.io:aud": "aws.workload.identity",
"app.terraform.io:sub":
`organization:seahaven:project:seahaven-prod:workspace:mta-sts-prod:run_phase:${phase}`,
},
},
},
],
});
}
function servicesPolicy(
account: string,
buckets: string[],
deployParams: string,
wafParam: string,
githubOidc: string,
): object {
const distributions = `arn:aws:cloudfront::${account}:distribution/*`;
const oacs = `arn:aws:cloudfront::${account}:origin-access-control/*`;
return {
Version: "2012-10-17",
Statement: [
{
Sid: "OriginBuckets",
Effect: "Allow",
Action: "s3:*",
Resource: buckets,
},
{
Sid: "ReadGithubOidcProvider",
Effect: "Allow",
Action: "iam:GetOpenIDConnectProvider",
Resource: githubOidc,
},
{
Sid: "CloudFrontRead",
Effect: "Allow",
Action: [
"cloudfront:GetDistribution",
"cloudfront:GetDistributionConfig",
"cloudfront:GetInvalidation",
"cloudfront:GetOriginAccessControl",
"cloudfront:ListTagsForResource",
],
Resource: "*",
},
{
// The provider calls the CreateDistributionWithTags API, authorized
// as cloudfront:CreateDistribution. That action has no resource type
// and only accepts Resource "*". Request tags come from the
// workspace's default_tags.
Sid: "CloudFrontCreateTagged",
Effect: "Allow",
Action: "cloudfront:CreateDistribution",
Resource: "*",
Condition: { StringEquals: { "aws:RequestTag/Project": "mta-sts" } },
},
{
// Tagging at create time, before the distribution has any tags. The
// Null condition keeps this off every distribution that already has
// a Project tag, so another workspace's distribution cannot be
// re-tagged into CloudFrontManageTagged's scope.
Sid: "CloudFrontTagUntagged",
Effect: "Allow",
Action: "cloudfront:TagResource",
Resource: distributions,
Condition: {
StringEquals: { "aws:RequestTag/Project": "mta-sts" },
Null: { "aws:ResourceTag/Project": "true" },
},
},
{
// Mutation of a distribution another workspace owns is denied by the
// resource tag, the same pattern AcmManageTagged uses.
Sid: "CloudFrontManageTagged",
Effect: "Allow",
Action: [
"cloudfront:CreateInvalidation",
"cloudfront:DeleteDistribution",
"cloudfront:TagResource",
"cloudfront:UntagResource",
"cloudfront:UpdateDistribution",
],
Resource: distributions,
Condition: { StringEquals: { "aws:ResourceTag/Project": "mta-sts" } },
},
{
// CreateOriginAccessControl has no resource type; Resource "*" only.
Sid: "CloudFrontCreateOac",
Effect: "Allow",
Action: "cloudfront:CreateOriginAccessControl",
Resource: "*",
},
{
// Origin access controls do not support tags, so the OAC ARN type is
// the tightest available scope.
Sid: "CloudFrontManageOac",
Effect: "Allow",
Action: [
"cloudfront:DeleteOriginAccessControl",
"cloudfront:UpdateOriginAccessControl",
],
Resource: oacs,
},
{
Sid: "AcmCreate",
Effect: "Allow",
Action: "acm:RequestCertificate",
Resource: "*",
Condition: { StringEquals: { "aws:RequestTag/Project": "mta-sts" } },
},
{
Sid: "AcmListTags",
Effect: "Allow",
Action: "acm:ListTagsForCertificate",
Resource: "*",
},
{
// The aws_acm_certificate resource reads with DescribeCertificate and
// reconciles tags with Add and Remove. No GetCertificate, Renew, or
// ListCertificates; those belong to the data source and early renewal.
Sid: "AcmManageTagged",
Effect: "Allow",
Action: [
"acm:AddTagsToCertificate",
"acm:DeleteCertificate",
"acm:DescribeCertificate",
"acm:RemoveTagsFromCertificate",
],
Resource: "*",
Condition: { StringEquals: { "aws:ResourceTag/Project": "mta-sts" } },
},
{
Sid: "ReadAppWebAclSsm",
Effect: "Allow",
Action: ["ssm:GetParameter", "ssm:GetParameters"],
Resource: wafParam,
},
{
Sid: "WriteDeployContract",
Effect: "Allow",
Action: [
"ssm:AddTagsToResource",
"ssm:DeleteParameter",
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:ListTagsForResource",
"ssm:PutParameter",
"ssm:RemoveTagsFromResource",
],
Resource: deployParams,
},
{
Sid: "DescribeParameters",
Effect: "Allow",
Action: "ssm:DescribeParameters",
Resource: "*",
},
{
Sid: "ReadWafWebAcl",
Effect: "Allow",
Action: [
"wafv2:GetWebACL",
"wafv2:GetWebACLForResource",
"wafv2:ListResourcesForWebACL",
"wafv2:ListWebACLs",
],
Resource: "*",
},
],
};
}
function scopedIamPolicy(account: string, deployRole: string, boundary: string): object {
return {
Version: "2012-10-17",
Statement: [
{
Sid: "DenyUntaggedCreatePolicy",
Effect: "Deny",
Action: "iam:CreatePolicy",
Resource: "*",
Condition: { Null: { "aws:RequestTag/BoundaryFor": "true" } },
},
{
Sid: "DenyOtherCreatePolicy",
Effect: "Deny",
Action: "iam:CreatePolicy",
Resource: "*",
Condition: {
StringNotEquals: { "aws:RequestTag/BoundaryFor": "githubdeploy-mta-sts" },
},
},
{
Sid: "DenyOtherPolicyVersions",
Effect: "Deny",
Action: [
"iam:CreatePolicyVersion",
"iam:DeletePolicy",
"iam:DeletePolicyVersion",
"iam:SetDefaultPolicyVersion",
],
NotResource: boundary,
},
{
// Only the boundary ARN may be created. The request tag stays as a
// second gate so the two Deny statements above keep their meaning.
Sid: "CreateDeployBoundary",
Effect: "Allow",
Action: "iam:CreatePolicy",
Resource: boundary,
Condition: {
StringEquals: { "aws:RequestTag/BoundaryFor": "githubdeploy-mta-sts" },
},
},
{
Sid: "ManageDeployBoundary",
Effect: "Allow",
Action: [
"iam:CreatePolicyVersion",
"iam:DeletePolicy",
"iam:DeletePolicyVersion",
"iam:GetPolicy",
"iam:GetPolicyVersion",
"iam:ListPolicyTags",
"iam:SetDefaultPolicyVersion",
"iam:TagPolicy",
"iam:UntagPolicy",
],
Resource: boundary,
},
{
// Fresh role. Creation requires the deploy boundary to be set.
Sid: "CreateDeployRoleWithBoundary",
Effect: "Allow",
Action: "iam:CreateRole",
Resource: deployRole,
Condition: { StringEquals: { "iam:PermissionsBoundary": boundary } },
},
{
Sid: "WriteDeployRoles",
Effect: "Allow",
Action: [
"iam:AttachRolePolicy",
"iam:DeleteRole",
"iam:DeleteRolePolicy",
"iam:DetachRolePolicy",
"iam:PutRolePolicy",
"iam:TagRole",
"iam:UntagRole",
"iam:UpdateAssumeRolePolicy",
"iam:UpdateRole",
"iam:UpdateRoleDescription",
],
Resource: deployRole,
},
{
Sid: "PutDeployRoleBoundary",
Effect: "Allow",
Action: "iam:PutRolePermissionsBoundary",
Resource: deployRole,
Condition: { StringEquals: { "iam:PermissionsBoundary": boundary } },
},
{
Sid: "IamReadOnly",
Effect: "Allow",
Action: [
"iam:GetPolicy",
"iam:GetPolicyVersion",
"iam:GetRole",
"iam:GetRolePolicy",
"iam:ListAttachedRolePolicies",
"iam:ListInstanceProfilesForRole",
"iam:ListPolicies",
"iam:ListPolicyVersions",
"iam:ListRolePolicies",
"iam:ListRoleTags",
"iam:ListRoles",
],
Resource: "*",
},
{
Sid: "DenySelfMutation",
Effect: "Deny",
Action: [
"iam:AttachRolePolicy",
"iam:DeleteRole",
"iam:DeleteRolePolicy",
"iam:DeleteRolePermissionsBoundary",
"iam:DetachRolePolicy",
"iam:PutRolePolicy",
"iam:PutRolePermissionsBoundary",
"iam:UpdateAssumeRolePolicy",
"iam:UpdateRole",
"iam:UpdateRoleDescription",
],
Resource: [
`arn:aws:iam::${account}:role/hcptf-*`,
`arn:aws:iam::${account}:role/github-cfn-execution-role`,
`arn:aws:iam::${account}:role/githubdeploy-*`,
`arn:aws:iam::${account}:role/cdk-hnb659fds-*`,
`arn:aws:iam::${account}:role/OrganizationAccountAccessRole`,
`arn:aws:iam::${account}:role/seahaven-*`,
],
},
{
Sid: "DenyBoundaryTampering",
Effect: "Deny",
Action: ["iam:DeleteRolePermissionsBoundary", "iam:DeleteUserPermissionsBoundary"],
Resource: [`arn:aws:iam::${account}:role/*`, `arn:aws:iam::${account}:user/*`],
},
{
Sid: "DenyBoundaryPolicyEdit",
Effect: "Deny",
Action: [
"iam:CreatePolicyVersion",
"iam:DeletePolicy",
"iam:DeletePolicyVersion",
"iam:SetDefaultPolicyVersion",
],
Resource: `arn:aws:iam::${account}:policy/seahaven-*`,
},
],
};
}
function planPolicy(
buckets: string[],
deployParams: string,
wafParam: string,
githubOidc: string,
deployRole: string,
boundary: string,
): object {
return {
Version: "2012-10-17",
Statement: [
{
Sid: "RefreshDeployRole",
Effect: "Allow",
Action: [
"iam:GetRole",
"iam:GetRolePolicy",
"iam:ListAttachedRolePolicies",
"iam:ListRolePolicies",
"iam:ListRoleTags",
],
Resource: deployRole,
},
{
Sid: "RefreshGithubOidcProvider",
Effect: "Allow",
Action: "iam:GetOpenIDConnectProvider",
Resource: githubOidc,
},
{
// The boundary is the only managed policy in Terraform state.
// ViewOnlyAccess carries iam:List* but not GetPolicy or
// GetPolicyVersion, so those are granted here on the exact ARN.
Sid: "RefreshDeployBoundary",
Effect: "Allow",
Action: [
"iam:GetPolicy",
"iam:GetPolicyVersion",
"iam:ListPolicyTags",
"iam:ListPolicyVersions",
],
Resource: boundary,
},
{
Sid: "RefreshOriginBuckets",
Effect: "Allow",
Action: [
"s3:GetAccelerateConfiguration",
"s3:GetBucketAcl",
"s3:GetBucketCORS",
"s3:GetBucketLocation",
"s3:GetBucketLogging",
"s3:GetBucketObjectLockConfiguration",
"s3:GetBucketOwnershipControls",
"s3:GetBucketPolicy",
"s3:GetBucketPolicyStatus",
"s3:GetBucketPublicAccessBlock",
"s3:GetBucketRequestPayment",
"s3:GetBucketTagging",
"s3:GetBucketVersioning",
"s3:GetBucketWebsite",
"s3:GetEncryptionConfiguration",
"s3:GetLifecycleConfiguration",
"s3:GetReplicationConfiguration",
"s3:ListBucket",
],
Resource: buckets,
},
{
Sid: "RefreshCloudFront",
Effect: "Allow",
Action: [
"cloudfront:GetDistribution",
"cloudfront:GetDistributionConfig",
"cloudfront:GetOriginAccessControl",
"cloudfront:ListTagsForResource",
],
Resource: "*",
},
{
Sid: "RefreshAcm",
Effect: "Allow",
Action: ["acm:DescribeCertificate", "acm:ListTagsForCertificate"],
Resource: "*",
},
{
Sid: "RefreshSsm",
Effect: "Allow",
Action: ["ssm:GetParameter", "ssm:GetParameters", "ssm:ListTagsForResource"],
Resource: [wafParam, deployParams],
},
{
// DescribeParameters accepts only Resource "*". The AWS provider
// calls it while refreshing aws_ssm_parameter.
Sid: "DescribeParameters",
Effect: "Allow",
Action: "ssm:DescribeParameters",
Resource: "*",
},
{
Sid: "RefreshWafWebAcl",
Effect: "Allow",
Action: ["wafv2:GetWebACL", "wafv2:ListWebACLs"],
Resource: "*",
},
],
};
}

View file

@ -2,12 +2,13 @@ import * as cdk from "aws-cdk-lib";
import * as iam from "aws-cdk-lib/aws-iam";
import { Construct } from "constructs";
import { HcptfPolicyAspect } from "./hcptf-policy-aspect";
import { MtaStsRoles } from "./mta-sts-hcptf-stack";
import { SeahavenSiteRoles } from "./seahaven-site-hcptf-stack";
/**
* HCP exec roles. One stack per account. Prod is 011934824531 and also
* hosts the seahaven-site apply and plan roles. Dev is 710827005802 and
* stays payments-dashboard only.
* hosts the seahaven-site and mta-sts apply and plan roles. Dev is
* 710827005802 and stays payments-dashboard only.
*
* payments-dashboard: workspace payments-dashboard-prod, project
* seahaven-prod, or workspace payments-dashboard-dev, project seahaven-dev.
@ -48,6 +49,8 @@ export interface SeahavenHcptfStackProps extends cdk.StackProps {
* `-c hcptfSiteImport=true`. Omits site role tags and site outputs.
*/
siteImportExisting?: boolean;
/** Prod only. Fold mta-sts exec roles into this stack (PLAT-243). */
includeMtaSts?: boolean;
}
export class SeahavenHcptfStack extends cdk.Stack {
@ -62,6 +65,9 @@ export class SeahavenHcptfStack extends cdk.Stack {
importExisting: props.siteImportExisting === true,
});
}
if (props.includeMtaSts) {
new MtaStsRoles(this, "MtaSts");
}
cdk.Aspects.of(this).add(new HcptfPolicyAspect(props.importExisting === true));
}
}