seahaven-org-baseline/lib/seahaven-hcptf-stack.ts

1281 lines
35 KiB
TypeScript
Raw Normal View History

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";
feat(hcptf): add hcptf-mta-sts apply and plan roles to seahaven-hcptf (PLAT-243) (#178) * 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.
2026-10-05 18:42:33 +00:00
import { MtaStsRoles } from "./mta-sts-hcptf-stack";
import { PaychexIntegrationsRoles } from "./paychex-integrations-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, mta-sts, and paychex-integrations 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.
*
* hcptf-payments-dashboard, hcptf-payments-dashboard-plan, and
* payments-dashboard-lambda-boundary already existed in both accounts and
* were imported. A create fails with a name conflict. The stacks are on
* the dev and prod deploy jobs. That deploy creates the three managed
* policies and removes the inline policies.
*
* `cdk import -c hcptfPaymentsImport=true` synthesizes only those three
* resources, with the roles' current inline policy names and no reference
* to the policies that do not exist yet. The default template is the
* managed-policy state.
*/
export interface SeahavenHcptfStackProps extends cdk.StackProps {
/** HCP project name: seahaven-prod or seahaven-dev. */
hcpProject: string;
/** HCP workspace name: payments-dashboard-prod or payments-dashboard-dev. */
hcpWorkspace: string;
/** DynamoDB CMK in this account. */
dynamodbCmkArn: string;
/**
* Secret ARNs in this order: slack-bot-token, slack-signing-secret,
* boa-check-mgmt, boa-reporting, expense-slack-token,
* expense-slack-signing-secret.
*/
secretArns: readonly string[];
/**
* Synthesize the import template. Set from `-c hcptfPaymentsImport=true`.
* Default is the managed-policy template.
*/
importExisting?: boolean;
/** Prod only. Fold seahaven-site exec roles into this stack. */
includeSeahavenSite?: boolean;
/**
* Synthesize the seahaven-site import template. Set from
* `-c hcptfSiteImport=true`. Omits site role tags and site outputs.
*/
siteImportExisting?: boolean;
feat(hcptf): add hcptf-mta-sts apply and plan roles to seahaven-hcptf (PLAT-243) (#178) * 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.
2026-10-05 18:42:33 +00:00
/** Prod only. Fold mta-sts exec roles into this stack (PLAT-243). */
includeMtaSts?: boolean;
/** Prod only. Fold paychex-integrations exec roles into this stack (PLAT-251). */
includePaychexIntegrations?: boolean;
/**
* Synthesize the paychex-integrations import template. Set from
* `-c hcptfPaychexImport=true`. Names the live inline policies and omits
* role tags and outputs.
*/
paychexImportExisting?: boolean;
}
export class SeahavenHcptfStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: SeahavenHcptfStackProps) {
super(scope, id, props);
const importFlags = [
props.importExisting,
props.siteImportExisting,
props.paychexImportExisting,
].filter((flag) => flag === true);
if (importFlags.length > 1) {
throw new Error(
"hcptfPaymentsImport, hcptfSiteImport, and hcptfPaychexImport cannot be combined",
);
}
paymentsDashboard(this, props);
if (props.includeSeahavenSite) {
new SeahavenSiteRoles(this, "SeahavenSite", {
importExisting: props.siteImportExisting === true,
});
}
feat(hcptf): add hcptf-mta-sts apply and plan roles to seahaven-hcptf (PLAT-243) (#178) * 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.
2026-10-05 18:42:33 +00:00
if (props.includeMtaSts) {
new MtaStsRoles(this, "MtaSts");
}
if (props.includePaychexIntegrations) {
new PaychexIntegrationsRoles(this, "PaychexIntegrations", {
importExisting: props.paychexImportExisting === true,
});
}
const allowInlinePolicies =
props.importExisting === true || props.paychexImportExisting === true;
cdk.Aspects.of(this).add(new HcptfPolicyAspect(allowInlinePolicies));
}
}
const VIEW_ONLY = "arn:aws:iam::aws:policy/job-function/ViewOnlyAccess";
/** Account and CMK id baked into the policy literals. Retarget rewrites them. */
const TEMPLATE_ACCOUNT = "011934824531";
const TEMPLATE_CMK_ID = "be5fa4cb-c546-40fe-a13d-c7bec79f5d12";
const TEMPLATE_SECRET_ARNS = [
"arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/slack-bot-token-0pAM3S",
"arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/slack-signing-secret-u0T6h8",
"arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/boa-check-mgmt-LEbC65",
"arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/boa-reporting-JoR9lq",
"arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/expense-slack-token-SeMg3s",
"arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/expense-slack-signing-secret-lbb78J",
] as const;
function retarget(
document: object,
account: string,
target: SeahavenHcptfStackProps,
): object {
if (target.secretArns.length !== TEMPLATE_SECRET_ARNS.length) {
throw new Error("payments-dashboard secretArns must list six secrets");
}
const cmkId = target.dynamodbCmkArn.split("/").pop();
if (!cmkId || !target.dynamodbCmkArn.includes(":key/")) {
throw new Error("dynamodbCmkArn must be a KMS key ARN");
}
let json = JSON.stringify(document);
const containedCmk = json.includes(TEMPLATE_CMK_ID);
for (let i = 0; i < TEMPLATE_SECRET_ARNS.length; i++) {
json = json.replaceAll(TEMPLATE_SECRET_ARNS[i], target.secretArns[i]);
}
json = json.replaceAll(TEMPLATE_CMK_ID, cmkId);
json = json.replaceAll(TEMPLATE_ACCOUNT, account);
if (containedCmk && !json.includes(target.dynamodbCmkArn)) {
throw new Error("CMK retarget did not produce dynamodbCmkArn");
}
return JSON.parse(json);
}
function paymentsDashboard(
stack: cdk.Stack,
target: SeahavenHcptfStackProps,
): void {
const account = stack.account;
const providerArn = `arn:aws:iam::${account}:oidc-provider/app.terraform.io`;
const workspace = `organization:seahaven:project:${target.hcpProject}:workspace:${target.hcpWorkspace}`;
// CloudFormation rejects Tags and any RoleArn output on an IAM role import.
// The deploy after import adds both.
if (!target.importExisting) {
cdk.Tags.of(stack).add("Project", "payments-dashboard");
cdk.Tags.of(stack).add("Owner", "adam@seahavenind.com");
cdk.Tags.of(stack).add("ManagedBy", "cdk");
}
const boundary = managedPolicy(
stack,
"PaymentsDashboardLambdaBoundary",
"payments-dashboard-lambda-boundary",
boundaryDocument(account, target),
"Per-workload Lambda permissions boundary for payments-dashboard (PLAT-79).",
);
const applyTrust = trust(
providerArn,
`${workspace}:run_phase:apply`,
"HcpApply",
);
const planTrust = trust(providerArn, `${workspace}:run_phase:plan`, "HcpPlan");
// Import template: the two roles and the boundary only. Inline policy names
// match the live roles so the later deploy can delete them. No Ref to the
// three managed policies that do not exist yet.
const apply = new iam.CfnRole(stack, "PaymentsDashboardApplyRole", {
roleName: "hcptf-payments-dashboard",
maxSessionDuration: 3600,
assumeRolePolicyDocument: applyTrust,
...(target.importExisting
? {
policies: [
{
policyName: "payments-dashboard-services",
policyDocument: servicesDocument(account, target),
},
{
policyName: "scoped-iam-management",
policyDocument: scopedIamDocument(account, target),
},
],
}
: {
managedPolicyArns: [
managedPolicy(
stack,
"PaymentsDashboardIam",
"payments-dashboard-hcptf-iam",
scopedIamDocument(account, target),
).ref,
managedPolicy(
stack,
"PaymentsDashboardServices",
"payments-dashboard-hcptf-services",
servicesDocument(account, target),
).ref,
],
}),
...(target.importExisting ? {} : { tags: roleTags() }),
});
retain(apply);
const plan = new iam.CfnRole(stack, "PaymentsDashboardPlanRole", {
roleName: "hcptf-payments-dashboard-plan",
maxSessionDuration: 3600,
assumeRolePolicyDocument: planTrust,
...(target.importExisting
? {
managedPolicyArns: [VIEW_ONLY],
policies: [
{
policyName: "payments-dashboard-plan-refresh",
policyDocument: planDocument(account, target),
},
],
}
: {
managedPolicyArns: [
VIEW_ONLY,
managedPolicy(
stack,
"PaymentsDashboardPlan",
"payments-dashboard-hcptf-plan",
planDocument(account, target),
).ref,
],
}),
...(target.importExisting ? {} : { tags: roleTags() }),
});
retain(plan);
if (!target.importExisting) {
new cdk.CfnOutput(stack, "ApplyRoleArn", { value: apply.attrArn });
new cdk.CfnOutput(stack, "PlanRoleArn", { value: plan.attrArn });
new cdk.CfnOutput(stack, "LambdaBoundaryArn", { value: boundary.ref });
}
}
function managedPolicy(
scope: Construct,
id: string,
name: string,
policyDocument: object,
description?: string,
): iam.CfnManagedPolicy {
const policy = new iam.CfnManagedPolicy(scope, id, {
managedPolicyName: name,
path: "/tf-managed/",
policyDocument,
...(description === undefined ? {} : { description }),
});
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: "payments-dashboard" },
{ key: "Owner", value: "adam@seahavenind.com" },
{ key: "ManagedBy", value: "cdk" },
];
}
function trust(providerArn: string, sub: string, sid: string): object {
return {
Version: "2012-10-17",
Statement: [
{
Sid: sid,
Effect: "Allow",
Action: "sts:AssumeRoleWithWebIdentity",
Principal: { Federated: providerArn },
Condition: {
StringEquals: {
"app.terraform.io:aud": "aws.workload.identity",
"app.terraform.io:sub": sub,
},
},
},
],
};
}
function scopedIamDocument(
account: string,
target: SeahavenHcptfStackProps,
): object {
return retarget({
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyCreatePolicy",
"Effect": "Deny",
"Action": [
"iam:CreatePolicy",
"iam:CreatePolicyVersion",
"iam:DeletePolicy",
"iam:DeletePolicyVersion",
"iam:SetDefaultPolicyVersion"
],
"Resource": [
"*"
]
},
{
"Sid": "CreateExecRoleWithBoundary",
"Effect": "Allow",
"Action": [
"iam:CreateRole"
],
"Resource": [
"arn:aws:iam::011934824531:role/tf-managed/payments-dashboard-*"
],
"Condition": {
"StringLike": {
"iam:PermissionsBoundary": [
"arn:aws:iam::011934824531:policy/tf-managed/payments-dashboard-*",
"arn:aws:iam::011934824531:policy/seahaven-lambda-execution-boundary",
"arn:aws:iam::011934824531:policy/seahaven-lambda-execution-boundary-payments-dashboard"
]
}
}
},
{
"Sid": "MutateExecRoleWithBoundary",
"Effect": "Allow",
"Action": [
"iam:AttachRolePolicy",
"iam:PutRolePolicy",
"iam:PutRolePermissionsBoundary"
],
"Resource": [
"arn:aws:iam::011934824531:role/tf-managed/payments-dashboard-*"
],
"Condition": {
"StringLike": {
"iam:PermissionsBoundary": [
"arn:aws:iam::011934824531:policy/tf-managed/payments-dashboard-*",
"arn:aws:iam::011934824531:policy/seahaven-lambda-execution-boundary",
"arn:aws:iam::011934824531:policy/seahaven-lambda-execution-boundary-payments-dashboard"
]
}
}
},
{
"Sid": "WriteExecRoles",
"Effect": "Allow",
"Action": [
"iam:DeleteRole",
"iam:DeleteRolePolicy",
"iam:DetachRolePolicy",
"iam:TagRole",
"iam:UntagRole",
"iam:UpdateAssumeRolePolicy",
"iam:UpdateRole",
"iam:UpdateRoleDescription"
],
"Resource": [
"arn:aws:iam::011934824531:role/tf-managed/payments-dashboard-*"
]
},
{
"Sid": "PassExecRolesToLambda",
"Effect": "Allow",
"Action": [
"iam:PassRole"
],
"Resource": [
"arn:aws:iam::011934824531:role/tf-managed/payments-dashboard-*"
],
"Condition": {
"StringEquals": {
"iam:PassedToService": [
"lambda.amazonaws.com"
]
}
}
},
{
"Sid": "CreateDeployRole",
"Effect": "Allow",
"Action": [
"iam:CreateRole"
],
"Resource": [
"arn:aws:iam::011934824531:role/tf-managed/githubdeploy-payments-dashboard"
],
"Condition": {
"Null": {
"iam:PermissionsBoundary": [
"true"
]
}
}
},
{
"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": [
"arn:aws:iam::011934824531:role/tf-managed/githubdeploy-payments-dashboard"
]
},
{
"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::011934824531:role/hcptf-*",
"arn:aws:iam::011934824531:role/github-cfn-execution-role",
"arn:aws:iam::011934824531:role/githubdeploy-*",
"arn:aws:iam::011934824531:role/cdk-hnb659fds-*",
"arn:aws:iam::011934824531:role/OrganizationAccountAccessRole",
"arn:aws:iam::011934824531:role/seahaven-*"
]
},
{
"Sid": "DenyBoundaryTampering",
"Effect": "Deny",
"Action": [
"iam:DeleteRolePermissionsBoundary",
"iam:DeleteUserPermissionsBoundary"
],
"Resource": [
"arn:aws:iam::011934824531:role/*",
"arn:aws:iam::011934824531:user/*"
]
},
{
"Sid": "DenyBoundaryPolicyEdit",
"Effect": "Deny",
"Action": [
"iam:CreatePolicyVersion",
"iam:DeletePolicy",
"iam:DeletePolicyVersion",
"iam:SetDefaultPolicyVersion"
],
"Resource": [
"arn:aws:iam::011934824531:policy/seahaven-*"
]
}
]
}, account, target);
}
function servicesDocument(
account: string,
target: SeahavenHcptfStackProps,
): object {
return retarget({
"Version": "2012-10-17",
"Statement": [
{
"Sid": "LambdaAll",
"Effect": "Allow",
"Action": [
"lambda:*"
],
"Resource": [
"arn:aws:lambda:us-east-1:011934824531:function:payments-*"
]
},
{
"Sid": "LambdaList",
"Effect": "Allow",
"Action": [
"lambda:ListFunctions",
"lambda:ListLayers",
"lambda:GetAccountSettings"
],
"Resource": [
"*"
]
},
{
"Sid": "EventBridgeRules",
"Effect": "Allow",
"Action": [
"events:*"
],
"Resource": [
"arn:aws:events:us-east-1:011934824531:rule/payments-dashboard-*"
]
},
{
"Sid": "EventBridgeList",
"Effect": "Allow",
"Action": [
"events:ListRules",
"events:ListRuleNamesByTarget"
],
"Resource": [
"*"
]
},
{
"Sid": "CloudWatchLogs",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:DeleteLogGroup",
"logs:PutRetentionPolicy",
"logs:DeleteRetentionPolicy",
"logs:TagResource",
"logs:UntagResource",
"logs:ListTagsForResource",
"logs:PutMetricFilter",
"logs:DeleteMetricFilter",
"logs:DescribeMetricFilters"
],
"Resource": [
"arn:aws:logs:us-east-1:011934824531:log-group:/aws/lambda/payments-*",
"arn:aws:logs:us-east-1:011934824531:log-group:/aws/apigateway/payments-dashboard",
"arn:aws:logs:us-east-1:011934824531:log-group:/aws/apigateway/payments-dashboard:*"
]
},
{
"Sid": "CloudWatchLogsDescribe",
"Effect": "Allow",
"Action": [
"logs:DescribeLogGroups"
],
"Resource": [
"*"
]
},
{
"Sid": "ApiGwAccessLogDelivery",
"Effect": "Allow",
"Action": [
"logs:CreateLogDelivery",
"logs:GetLogDelivery",
"logs:UpdateLogDelivery",
"logs:DeleteLogDelivery",
"logs:ListLogDeliveries",
"logs:PutResourcePolicy",
"logs:DescribeResourcePolicies"
],
"Resource": [
"*"
]
},
{
"Sid": "StackBuckets",
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::payments-dashboard-artifacts-011934824531",
"arn:aws:s3:::payments-dashboard-artifacts-011934824531/*",
"arn:aws:s3:::seahaven-payments-csv-011934824531",
"arn:aws:s3:::seahaven-payments-csv-011934824531/*",
"arn:aws:s3:::seahaven-payments-boa-raw-011934824531",
"arn:aws:s3:::seahaven-payments-boa-raw-011934824531/*"
]
},
{
"Sid": "DynamoDBTable",
"Effect": "Allow",
"Action": [
"dynamodb:*"
],
"Resource": [
"arn:aws:dynamodb:us-east-1:011934824531:table/PaymentsDashboard",
"arn:aws:dynamodb:us-east-1:011934824531:table/PaymentsDashboard/*"
]
},
{
"Sid": "DynamoDBList",
"Effect": "Allow",
"Action": [
"dynamodb:ListTables"
],
"Resource": [
"*"
]
},
{
"Sid": "SqsDlq",
"Effect": "Allow",
"Action": [
"sqs:*"
],
"Resource": [
"arn:aws:sqs:us-east-1:011934824531:payments-processPaymentCsv-async-dlq"
]
},
{
"Sid": "SqsList",
"Effect": "Allow",
"Action": [
"sqs:ListQueues"
],
"Resource": [
"*"
]
},
{
"Sid": "HttpApiManage",
"Effect": "Allow",
"Action": [
"apigateway:*"
],
"Resource": [
"arn:aws:apigateway:us-east-1::/apis",
"arn:aws:apigateway:us-east-1::/apis/*",
"arn:aws:apigateway:us-east-1::/tags/*",
"arn:aws:apigateway:us-east-1::/vpclinks",
"arn:aws:apigateway:us-east-1::/vpclinks/*"
]
},
{
"Sid": "PaymentsSsm",
"Effect": "Allow",
"Action": [
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:PutParameter",
"ssm:DeleteParameter",
"ssm:AddTagsToResource",
"ssm:RemoveTagsFromResource",
"ssm:ListTagsForResource"
],
"Resource": [
"arn:aws:ssm:us-east-1:011934824531:parameter/payments-dashboard/*",
"arn:aws:ssm:us-east-1:011934824531:parameter/seahaven/dynamodb/cmk-arn"
]
},
{
"Sid": "SsmDescribeParameters",
"Effect": "Allow",
"Action": [
"ssm:DescribeParameters"
],
"Resource": [
"*"
]
},
{
"Sid": "SecretsManagerRead",
"Effect": "Allow",
"Action": [
"secretsmanager:DescribeSecret",
"secretsmanager:GetResourcePolicy",
"secretsmanager:ListSecretVersionIds",
"secretsmanager:TagResource",
"secretsmanager:UntagResource"
],
"Resource": [
"arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/*"
]
},
{
"Sid": "SecretsManagerList",
"Effect": "Allow",
"Action": [
"secretsmanager:ListSecrets"
],
"Resource": [
"*"
]
},
{
"Sid": "KmsTableCmk",
"Effect": "Allow",
"Action": [
"kms:DescribeKey",
"kms:GetKeyPolicy",
"kms:ListResourceTags",
"kms:CreateGrant",
"kms:ListGrants",
"kms:RetireGrant",
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey",
"kms:GenerateDataKeyWithoutPlaintext"
],
"Resource": [
"arn:aws:kms:us-east-1:011934824531:key/be5fa4cb-c546-40fe-a13d-c7bec79f5d12"
]
},
{
"Sid": "CloudWatchAlarms",
"Effect": "Allow",
"Action": [
"cloudwatch:PutMetricAlarm",
"cloudwatch:DeleteAlarms",
"cloudwatch:DescribeAlarms",
"cloudwatch:TagResource",
"cloudwatch:UntagResource",
"cloudwatch:ListTagsForResource"
],
"Resource": [
"arn:aws:cloudwatch:us-east-1:011934824531:alarm:payments-*"
]
},
{
"Sid": "CloudWatchDescribeAlarms",
"Effect": "Allow",
"Action": [
"cloudwatch:DescribeAlarms"
],
"Resource": [
"*"
]
},
{
"Sid": "SnsPublishSiteAlerts",
"Effect": "Allow",
"Action": [
"sns:Publish",
"sns:GetTopicAttributes",
"sns:ListTagsForResource"
],
"Resource": [
"arn:aws:sns:us-east-1:011934824531:site-alerts"
]
},
{
"Sid": "ManageTfManagedBoundary",
"Effect": "Allow",
"Action": [
"iam:GetPolicy",
"iam:GetPolicyVersion",
"iam:ListPolicyVersions",
"iam:ListPolicyTags",
"iam:TagPolicy",
"iam:UntagPolicy"
],
"Resource": [
"arn:aws:iam::011934824531:policy/tf-managed/payments-dashboard-*"
]
},
{
"Sid": "Ec2VpcManagement",
"Effect": "Allow",
"Action": [
"ec2:AllocateAddress",
"ec2:AssociateRouteTable",
"ec2:AttachInternetGateway",
"ec2:AuthorizeSecurityGroupEgress",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:CreateInternetGateway",
"ec2:CreateNatGateway",
"ec2:CreateRoute",
"ec2:CreateRouteTable",
"ec2:CreateSecurityGroup",
"ec2:CreateSubnet",
"ec2:CreateVpc",
"ec2:CreateVpcEndpoint",
"ec2:CreateTags",
"ec2:DeleteInternetGateway",
"ec2:DeleteNatGateway",
"ec2:DeleteRoute",
"ec2:DeleteRouteTable",
"ec2:DeleteSecurityGroup",
"ec2:DeleteSubnet",
"ec2:DeleteVpc",
"ec2:DeleteVpcEndpoints",
"ec2:DescribeAccountAttributes",
"ec2:DescribeAddresses",
"ec2:DescribeAddressesAttribute",
"ec2:DescribeAvailabilityZones",
"ec2:DescribeInternetGateways",
"ec2:DescribeNatGateways",
"ec2:DescribeNetworkInterfaces",
"ec2:DescribeRouteTables",
"ec2:DescribeSecurityGroupRules",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSubnets",
"ec2:DescribeTags",
"ec2:DescribeVpcAttribute",
"ec2:DescribeVpcEndpoints",
"ec2:DescribeVpcs",
"ec2:DescribePrefixLists",
"ec2:DetachInternetGateway",
"ec2:DisassociateAddress",
"ec2:DisassociateRouteTable",
"ec2:ModifySubnetAttribute",
"ec2:ModifyVpcAttribute",
"ec2:ModifyVpcEndpoint",
"ec2:ReleaseAddress",
"ec2:RevokeSecurityGroupEgress",
"ec2:RevokeSecurityGroupIngress",
"ec2:UpdateSecurityGroupRuleDescriptionsEgress",
"ec2:UpdateSecurityGroupRuleDescriptionsIngress"
],
"Resource": [
"*"
]
}
]
}, account, target);
}
function planDocument(
account: string,
target: SeahavenHcptfStackProps,
): object {
return retarget({
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RefreshIamRoles",
"Effect": "Allow",
"Action": [
"iam:GetRole",
"iam:GetRolePolicy",
"iam:ListRolePolicies",
"iam:ListAttachedRolePolicies",
"iam:ListRoleTags"
],
"Resource": [
"arn:aws:iam::011934824531:role/tf-managed/payments-dashboard-*",
"arn:aws:iam::011934824531:role/tf-managed/githubdeploy-payments-dashboard",
"arn:aws:iam::011934824531:role/hcptf-payments-dashboard",
"arn:aws:iam::011934824531:role/hcptf-payments-dashboard-plan"
]
},
{
"Sid": "RefreshManagedPolicies",
"Effect": "Allow",
"Action": [
"iam:GetPolicy",
"iam:GetPolicyVersion"
],
"Resource": [
"*"
]
},
{
"Sid": "RefreshLambda",
"Effect": "Allow",
"Action": [
"lambda:GetFunction",
"lambda:GetFunctionConfiguration",
"lambda:GetPolicy",
"lambda:GetFunctionCodeSigningConfig",
"lambda:GetFunctionConcurrency",
"lambda:GetFunctionEventInvokeConfig",
"lambda:GetFunctionUrlConfig",
"lambda:GetRuntimeManagementConfig",
"lambda:GetFunctionRecursionConfig",
"lambda:ListTags",
"lambda:ListVersionsByFunction",
"lambda:ListAliases"
],
"Resource": [
"arn:aws:lambda:us-east-1:011934824531:function:payments-*"
]
},
{
"Sid": "RefreshLambdaList",
"Effect": "Allow",
"Action": [
"lambda:ListFunctions",
"lambda:ListLayers",
"lambda:GetAccountSettings"
],
"Resource": [
"*"
]
},
{
"Sid": "RefreshBuckets",
"Effect": "Allow",
"Action": [
"s3:GetAccelerateConfiguration",
"s3:GetAnalyticsConfiguration",
"s3:GetBucketAcl",
"s3:GetBucketCORS",
"s3:GetBucketLifecycleConfiguration",
"s3:GetBucketLocation",
"s3:GetBucketLogging",
"s3:GetBucketNotification",
"s3:GetBucketObjectLockConfiguration",
"s3:GetBucketOwnershipControls",
"s3:GetBucketPolicy",
"s3:GetBucketPolicyStatus",
"s3:GetBucketPublicAccessBlock",
"s3:GetBucketReplication",
"s3:GetBucketRequestPayment",
"s3:GetBucketTagging",
"s3:GetBucketVersioning",
"s3:GetBucketWebsite",
"s3:GetEncryptionConfiguration",
"s3:GetIntelligentTieringConfiguration",
"s3:GetInventoryConfiguration",
"s3:GetLifecycleConfiguration",
"s3:GetMetricsConfiguration",
"s3:GetObject",
"s3:GetObjectTagging",
"s3:GetObjectVersion",
"s3:GetReplicationConfiguration",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::payments-dashboard-artifacts-011934824531",
"arn:aws:s3:::payments-dashboard-artifacts-011934824531/*",
"arn:aws:s3:::seahaven-payments-csv-011934824531",
"arn:aws:s3:::seahaven-payments-csv-011934824531/*",
"arn:aws:s3:::seahaven-payments-boa-raw-011934824531",
"arn:aws:s3:::seahaven-payments-boa-raw-011934824531/*"
]
},
{
"Sid": "RefreshDynamoDB",
"Effect": "Allow",
"Action": [
"dynamodb:DescribeTable",
"dynamodb:DescribeTimeToLive",
"dynamodb:DescribeContinuousBackups",
"dynamodb:DescribeKinesisStreamingDestination",
"dynamodb:ListTagsOfResource"
],
"Resource": [
"arn:aws:dynamodb:us-east-1:011934824531:table/PaymentsDashboard"
]
},
{
"Sid": "RefreshEventBridge",
"Effect": "Allow",
"Action": [
"events:DescribeRule",
"events:ListTargetsByRule",
"events:ListTagsForResource"
],
"Resource": [
"arn:aws:events:us-east-1:011934824531:rule/payments-dashboard-*"
]
},
{
"Sid": "RefreshLogs",
"Effect": "Allow",
"Action": [
"logs:DescribeLogGroups",
"logs:ListTagsForResource"
],
"Resource": [
"*"
]
},
{
"Sid": "RefreshHttpApi",
"Effect": "Allow",
"Action": [
"apigateway:GET"
],
"Resource": [
"arn:aws:apigateway:us-east-1::/apis",
"arn:aws:apigateway:us-east-1::/apis/*",
"arn:aws:apigateway:us-east-1::/tags/*"
]
},
{
"Sid": "RefreshSsm",
"Effect": "Allow",
"Action": [
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:ListTagsForResource"
],
"Resource": [
"arn:aws:ssm:us-east-1:011934824531:parameter/payments-dashboard/*",
"arn:aws:ssm:us-east-1:011934824531:parameter/seahaven/dynamodb/cmk-arn"
]
},
{
"Sid": "RefreshSsmDescribeParameters",
"Effect": "Allow",
"Action": [
"ssm:DescribeParameters"
],
"Resource": [
"*"
]
},
{
"Sid": "RefreshSecrets",
"Effect": "Allow",
"Action": [
"secretsmanager:DescribeSecret",
"secretsmanager:GetResourcePolicy",
"secretsmanager:ListSecretVersionIds"
],
"Resource": [
"arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/*"
]
},
{
"Sid": "RefreshSecretsList",
"Effect": "Allow",
"Action": [
"secretsmanager:ListSecrets"
],
"Resource": [
"*"
]
},
{
"Sid": "RefreshAlarms",
"Effect": "Allow",
"Action": [
"cloudwatch:DescribeAlarms",
"cloudwatch:ListTagsForResource"
],
"Resource": [
"*"
]
},
{
"Sid": "RefreshSns",
"Effect": "Allow",
"Action": [
"sns:GetTopicAttributes",
"sns:ListTagsForResource"
],
"Resource": [
"arn:aws:sns:us-east-1:011934824531:site-alerts"
]
},
{
"Sid": "RefreshSqs",
"Effect": "Allow",
"Action": [
"sqs:GetQueueAttributes",
"sqs:GetQueueUrl",
"sqs:ListQueueTags"
],
"Resource": [
"arn:aws:sqs:us-east-1:011934824531:payments-processPaymentCsv-async-dlq"
]
},
{
"Sid": "RefreshKms",
"Effect": "Allow",
"Action": [
"kms:DescribeKey",
"kms:GetKeyPolicy",
"kms:ListResourceTags",
"kms:CreateGrant",
"kms:ListGrants"
],
"Resource": [
"arn:aws:kms:us-east-1:011934824531:key/be5fa4cb-c546-40fe-a13d-c7bec79f5d12"
]
},
{
"Sid": "RefreshEc2",
"Effect": "Allow",
"Action": [
"ec2:DescribeAccountAttributes",
"ec2:DescribeAddresses",
"ec2:DescribeAddressesAttribute",
"ec2:DescribeAvailabilityZones",
"ec2:DescribeInternetGateways",
"ec2:DescribeNatGateways",
"ec2:DescribeNetworkInterfaces",
"ec2:DescribeRouteTables",
"ec2:DescribeSecurityGroupRules",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSubnets",
"ec2:DescribeTags",
"ec2:DescribeVpcAttribute",
"ec2:DescribeVpcEndpoints",
"ec2:DescribeVpcs",
"ec2:DescribePrefixLists"
],
"Resource": [
"*"
]
}
]
}, account, target);
}
function boundaryDocument(
account: string,
target: SeahavenHcptfStackProps,
): object {
return retarget({
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CloudWatchLogsWrite",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogStreams"
],
"Resource": [
"arn:aws:logs:us-east-1:011934824531:log-group:/aws/lambda*"
]
},
{
"Sid": "CloudWatchLogsDescribe",
"Effect": "Allow",
"Action": [
"logs:DescribeLogGroups"
],
"Resource": [
"*"
]
},
{
"Sid": "XRay",
"Effect": "Allow",
"Action": [
"xray:PutTraceSegments",
"xray:PutTelemetryRecords"
],
"Resource": [
"*"
]
},
{
"Sid": "Ec2Eni",
"Effect": "Allow",
"Action": [
"ec2:CreateNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface",
"ec2:DescribeSubnets",
"ec2:DescribeSecurityGroups",
"ec2:DescribeVpcs"
],
"Resource": [
"*"
]
},
{
"Sid": "PaymentsSecrets",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": [
"arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/slack-bot-token-0pAM3S",
"arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/slack-signing-secret-u0T6h8",
"arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/boa-check-mgmt-LEbC65",
"arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/boa-reporting-JoR9lq",
"arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/expense-slack-token-SeMg3s",
"arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/expense-slack-signing-secret-lbb78J"
]
},
{
"Sid": "PaymentsDynamoDB",
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:BatchGetItem",
"dynamodb:BatchWriteItem",
"dynamodb:DescribeTable",
"dynamodb:ConditionCheckItem"
],
"Resource": [
"arn:aws:dynamodb:us-east-1:011934824531:table/PaymentsDashboard",
"arn:aws:dynamodb:us-east-1:011934824531:table/PaymentsDashboard/*"
]
},
{
"Sid": "PaymentsCmk",
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:GenerateDataKey",
"kms:DescribeKey"
],
"Resource": [
"arn:aws:kms:us-east-1:011934824531:key/be5fa4cb-c546-40fe-a13d-c7bec79f5d12"
],
"Condition": {
"StringEquals": {
"kms:ViaService": [
"dynamodb.us-east-1.amazonaws.com"
]
}
}
},
{
"Sid": "PaymentsCsvRead",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion"
],
"Resource": [
"arn:aws:s3:::seahaven-payments-csv-011934824531/*"
]
},
{
"Sid": "PaymentsBoaRawPut",
"Effect": "Allow",
"Action": [
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::seahaven-payments-boa-raw-011934824531/*"
]
},
{
"Sid": "PaymentsDlqSend",
"Effect": "Allow",
"Action": [
"sqs:SendMessage"
],
"Resource": [
"arn:aws:sqs:us-east-1:011934824531:payments-processPaymentCsv-async-dlq"
]
},
{
"Sid": "PaymentsInvokeExpenseProcessor",
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction"
],
"Resource": [
"arn:aws:lambda:us-east-1:011934824531:function:payments-expenseProcessor"
]
}
]
}, account, target);
}