mirror of
https://github.com/Sea-Haven-Industries/seahaven-org-baseline.git
synced 2026-10-06 16:51:59 +00:00
feat(hcptf): import hcptf-paychex-integrations apply and plan roles into seahaven-hcptf (PLAT-251) (#179)
* feat(hcptf): import hcptf-paychex-integrations apply and plan roles into seahaven-hcptf (PLAT-251) PaychexIntegrationsRoles is nested in the prod seahaven-hcptf stack for the paychex-integrations-prod workspace. The two roles already exist and are imported with -c hcptfPaychexImport=true, which names the live inline policies and omits role tags and outputs. The default template replaces the inline policies with managed policies at /tf-managed/: - paychex-integrations-hcptf-iam: the ported scoped IAM document plus CreateRole and the write set on tf-managed/githubdeploy-paychex-integrations (no permissions boundary) and iam:GetOpenIDConnectProvider. TagHcptfRoles is dropped; the roles are no longer Terraform-managed. - paychex-integrations-hcptf-services: the ported services document plus SSM writes on /paychex-integrations/deploy/* and DescribeParameters. - paychex-integrations-hcptf-plan: the ported refresh document plus the deploy role, the GitHub OIDC provider, and the deploy parameters. Trust is unchanged. Every resource is Retain. * docs(hcptf): describe the paychex-integrations import as a sequence * chore(ci): retrigger checks after the GitHub Actions incident
This commit is contained in:
parent
a43ec1f0f5
commit
227a5d91a2
5 changed files with 748 additions and 13 deletions
25
README.md
25
README.md
|
|
@ -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) 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-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), the created `hcptf-mta-sts` apply and plan roles (PLAT-243), and the imported `hcptf-paychex-integrations` apply and plan roles (PLAT-251), with policies at `/tf-managed/`. Trust is pinned per workspace. On the dev and prod deploy jobs. Do not create the payments-dashboard, seahaven-site, or paychex-integrations 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) |
|
||||
|
|
@ -240,15 +240,26 @@ 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. 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
|
||||
payments-dashboard, seahaven-site, mta-sts, and paychex-integrations all
|
||||
live in `seahaven-hcptf` (`lib/seahaven-hcptf-stack.ts`,
|
||||
`lib/seahaven-site-hcptf-stack.ts`, `lib/mta-sts-hcptf-stack.ts`,
|
||||
`lib/paychex-integrations-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 payments-dashboard roles, the boundary, or the seahaven-site
|
||||
roles by hand; they are imported. The mta-sts roles are a plain create.
|
||||
create the payments-dashboard roles, the boundary, the seahaven-site roles,
|
||||
or the paychex-integrations roles by hand; they are imported. The mta-sts
|
||||
roles are a plain create.
|
||||
|
||||
The paychex-integrations roles enter this stack by import, in this order:
|
||||
the paychex-integrations workspace forgets them with `removed` blocks, then
|
||||
`cdk import seahaven-hcptf -c hcptfPaychexImport=true --resource-mapping import-maps/paychex-integrations-into-hcptf-prod.json`
|
||||
adopts the two roles, then the default template deploys. That deploy creates
|
||||
`paychex-integrations-hcptf-iam`, `paychex-integrations-hcptf-services`, and
|
||||
`paychex-integrations-hcptf-plan` and removes the inline policies. The apply
|
||||
role may create `githubdeploy-paychex-integrations` at `/tf-managed/` and
|
||||
write SSM `/paychex-integrations/deploy/*`.
|
||||
|
||||
**External-dev IAM stays in this repo (PLAT-148).** SHOC backend/frontend HCP
|
||||
roles, SHOC deploy/runtime boundaries, `shoc-frontend-resources.ts`, and
|
||||
|
|
|
|||
|
|
@ -244,10 +244,13 @@ 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).
|
||||
// Prod also creates the mta-sts apply and plan roles (PLAT-243) and
|
||||
// includes the imported paychex-integrations apply and plan roles
|
||||
// (PLAT-251). Import paychex with `-c hcptfPaychexImport=true`.
|
||||
// Trust is pinned per workspace.
|
||||
const hcptfPaymentsImport = contextBoolean("hcptfPaymentsImport");
|
||||
const hcptfSiteImport = contextBoolean("hcptfSiteImport");
|
||||
const hcptfPaychexImport = contextBoolean("hcptfPaychexImport");
|
||||
const paymentsSecrets = (
|
||||
account: string,
|
||||
suffixes: readonly string[],
|
||||
|
|
@ -276,6 +279,8 @@ new SeahavenHcptfStack(app, "seahaven-hcptf", {
|
|||
includeSeahavenSite: true,
|
||||
siteImportExisting: hcptfSiteImport,
|
||||
includeMtaSts: true,
|
||||
includePaychexIntegrations: true,
|
||||
paychexImportExisting: hcptfPaychexImport,
|
||||
});
|
||||
|
||||
new SeahavenHcptfStack(app, "seahaven-hcptf-dev", {
|
||||
|
|
|
|||
8
import-maps/paychex-integrations-into-hcptf-prod.json
Normal file
8
import-maps/paychex-integrations-into-hcptf-prod.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"PaychexIntegrationsApplyRoleF0B199BB": {
|
||||
"RoleName": "hcptf-paychex-integrations"
|
||||
},
|
||||
"PaychexIntegrationsPlanRole71013409": {
|
||||
"RoleName": "hcptf-paychex-integrations-plan"
|
||||
}
|
||||
}
|
||||
688
lib/paychex-integrations-hcptf-stack.ts
Normal file
688
lib/paychex-integrations-hcptf-stack.ts
Normal file
|
|
@ -0,0 +1,688 @@
|
|||
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 paychex-integrations HCP workspace (PLAT-251).
|
||||
*
|
||||
* Nested in the prod seahaven-hcptf stack. `hcptf-paychex-integrations` and
|
||||
* `hcptf-paychex-integrations-plan` already exist. They were created by
|
||||
* create-hcptf-bootstrap-roles.sh and then managed by the paychex-integrations
|
||||
* workspace itself through a bootstrap credential swap. That workspace forgets
|
||||
* them with `removed` blocks before this construct imports them. Do not create
|
||||
* them. A plain create fails because the roles already exist.
|
||||
*
|
||||
* Import identifiers are the two role names. Construct ids stay ApplyRole and
|
||||
* PlanRole under PaychexIntegrations. The three /tf-managed/ managed policies
|
||||
* do not exist before the deploy that follows the import.
|
||||
*
|
||||
* `importExisting` is the `-c hcptfPaychexImport=true` template. It names the
|
||||
* roles' live inline policies (`paychex-integrations-services`,
|
||||
* `scoped-iam-management`, `paychex-integrations-plan-refresh`) so the
|
||||
* following deploy can delete them, and it omits role tags and the role ARN
|
||||
* outputs. CloudFormation rejects both on an IAM role import. The default
|
||||
* template is the managed-policy state.
|
||||
*
|
||||
* Beyond the ported documents, the apply role can create and manage
|
||||
* `githubdeploy-paychex-integrations` at /tf-managed/ with no permissions
|
||||
* boundary, and can write the deploy contract under SSM
|
||||
* /paychex-integrations/deploy/*. The plan role can refresh both.
|
||||
*/
|
||||
export interface PaychexIntegrationsRolesProps {
|
||||
/** Synthesize the import template. Set from `-c hcptfPaychexImport=true`. */
|
||||
importExisting?: boolean;
|
||||
}
|
||||
|
||||
const VIEW_ONLY = "arn:aws:iam::aws:policy/job-function/ViewOnlyAccess";
|
||||
const WORKSPACE =
|
||||
"organization:seahaven:project:seahaven-prod:workspace:paychex-integrations-prod";
|
||||
|
||||
export class PaychexIntegrationsRoles extends Construct {
|
||||
constructor(scope: Construct, id: string, props: PaychexIntegrationsRolesProps = {}) {
|
||||
super(scope, id);
|
||||
|
||||
const importing = props.importExisting === true;
|
||||
// Overrides the parent stack's Project=payments-dashboard tag.
|
||||
cdk.Tags.of(this).add("Project", "paychex-integrations", { priority: 200 });
|
||||
if (importing) {
|
||||
const roleOnly = { priority: 300, includeResourceTypes: ["AWS::IAM::Role"] };
|
||||
cdk.Tags.of(this).remove("Project", roleOnly);
|
||||
cdk.Tags.of(this).remove("Owner", roleOnly);
|
||||
cdk.Tags.of(this).remove("ManagedBy", roleOnly);
|
||||
}
|
||||
|
||||
const account = cdk.Stack.of(this).account;
|
||||
const hcpOidc = `arn:aws:iam::${account}:oidc-provider/app.terraform.io`;
|
||||
const deployRole = `arn:aws:iam::${account}:role/tf-managed/githubdeploy-paychex-integrations`;
|
||||
const deployParams = `arn:aws:ssm:us-east-1:${account}:parameter/paychex-integrations/deploy/*`;
|
||||
|
||||
const iamDocument = scopedIamPolicy(account, deployRole);
|
||||
const servicesDocument = servicesPolicy(account, deployParams);
|
||||
const planDocument = planPolicy(account, deployRole, deployParams);
|
||||
|
||||
const apply = new iam.CfnRole(this, "ApplyRole", {
|
||||
roleName: "hcptf-paychex-integrations",
|
||||
maxSessionDuration: 3600,
|
||||
assumeRolePolicyDocument: trust(hcpOidc, "apply"),
|
||||
...(importing
|
||||
? {
|
||||
policies: [
|
||||
{ policyName: "paychex-integrations-services", policyDocument: servicesDocument },
|
||||
{ policyName: "scoped-iam-management", policyDocument: iamDocument },
|
||||
],
|
||||
}
|
||||
: {
|
||||
managedPolicyArns: [
|
||||
managedPolicy(this, "IamPolicy", "paychex-integrations-hcptf-iam", iamDocument).ref,
|
||||
managedPolicy(
|
||||
this,
|
||||
"ServicesPolicy",
|
||||
"paychex-integrations-hcptf-services",
|
||||
servicesDocument,
|
||||
).ref,
|
||||
],
|
||||
tags: roleTags(),
|
||||
}),
|
||||
});
|
||||
retain(apply);
|
||||
|
||||
const plan = new iam.CfnRole(this, "PlanRole", {
|
||||
roleName: "hcptf-paychex-integrations-plan",
|
||||
maxSessionDuration: 3600,
|
||||
assumeRolePolicyDocument: trust(hcpOidc, "plan"),
|
||||
...(importing
|
||||
? {
|
||||
managedPolicyArns: [VIEW_ONLY],
|
||||
policies: [
|
||||
{ policyName: "paychex-integrations-plan-refresh", policyDocument: planDocument },
|
||||
],
|
||||
}
|
||||
: {
|
||||
managedPolicyArns: [
|
||||
VIEW_ONLY,
|
||||
managedPolicy(this, "PlanPolicy", "paychex-integrations-hcptf-plan", planDocument).ref,
|
||||
],
|
||||
tags: roleTags(),
|
||||
}),
|
||||
});
|
||||
retain(plan);
|
||||
|
||||
if (!importing) {
|
||||
const applyArn = new cdk.CfnOutput(this, "ApplyRoleArn", { value: apply.attrArn });
|
||||
const planArn = new cdk.CfnOutput(this, "PlanRoleArn", { value: plan.attrArn });
|
||||
applyArn.overrideLogicalId("PaychexIntegrationsApplyRoleArn");
|
||||
planArn.overrideLogicalId("PaychexIntegrationsPlanRoleArn");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: "paychex-integrations" },
|
||||
{ key: "Owner", value: "adam@seahavenind.com" },
|
||||
{ key: "ManagedBy", value: "cdk" },
|
||||
];
|
||||
}
|
||||
|
||||
function trust(providerArn: string, phase: "apply" | "plan"): object {
|
||||
return {
|
||||
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": `${WORKSPACE}:run_phase:${phase}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/** Ported from paychex-integrations terraform/hcp_iam.tf hcptf_scoped_iam plus the deploy role. */
|
||||
function scopedIamPolicy(account: string, deployRole: string): object {
|
||||
const execRoles = `arn:aws:iam::${account}:role/tf-managed/paychex-*`;
|
||||
const execBoundaries = [
|
||||
`arn:aws:iam::${account}:policy/tf-managed/paychex-*`,
|
||||
`arn:aws:iam::${account}:policy/seahaven-lambda-execution-boundary-paychex-integrations`,
|
||||
];
|
||||
return {
|
||||
Version: "2012-10-17",
|
||||
Statement: [
|
||||
{
|
||||
Sid: "DenyCreatePolicy",
|
||||
Effect: "Deny",
|
||||
Action: ["iam:CreatePolicy", "iam:CreatePolicyVersion"],
|
||||
Resource: "*",
|
||||
},
|
||||
{
|
||||
Sid: "CreateExecRoleWithBoundary",
|
||||
Effect: "Allow",
|
||||
Action: "iam:CreateRole",
|
||||
Resource: execRoles,
|
||||
Condition: { StringLike: { "iam:PermissionsBoundary": execBoundaries } },
|
||||
},
|
||||
{
|
||||
Sid: "MutateExecRoleWithBoundary",
|
||||
Effect: "Allow",
|
||||
Action: ["iam:AttachRolePolicy", "iam:PutRolePolicy", "iam:PutRolePermissionsBoundary"],
|
||||
Resource: execRoles,
|
||||
Condition: { StringLike: { "iam:PermissionsBoundary": execBoundaries } },
|
||||
},
|
||||
{
|
||||
Sid: "WriteExecRoles",
|
||||
Effect: "Allow",
|
||||
Action: [
|
||||
"iam:DeleteRole",
|
||||
"iam:DeleteRolePolicy",
|
||||
"iam:DetachRolePolicy",
|
||||
"iam:TagRole",
|
||||
"iam:UntagRole",
|
||||
"iam:UpdateAssumeRolePolicy",
|
||||
"iam:UpdateRole",
|
||||
"iam:UpdateRoleDescription",
|
||||
],
|
||||
Resource: execRoles,
|
||||
},
|
||||
{
|
||||
Sid: "PassExecRolesToLambda",
|
||||
Effect: "Allow",
|
||||
Action: "iam:PassRole",
|
||||
Resource: execRoles,
|
||||
Condition: { StringEquals: { "iam:PassedToService": "lambda.amazonaws.com" } },
|
||||
},
|
||||
{
|
||||
Sid: "PassPayrollScheduleToScheduler",
|
||||
Effect: "Allow",
|
||||
Action: "iam:PassRole",
|
||||
Resource: `arn:aws:iam::${account}:role/tf-managed/paychex-payroll-schedule-invoke`,
|
||||
Condition: { StringEquals: { "iam:PassedToService": "scheduler.amazonaws.com" } },
|
||||
},
|
||||
{
|
||||
// GitHub Actions deploy role, owned by the workspace. Path /tf-managed/
|
||||
// keeps it outside DenySelfMutation's role/githubdeploy-* pattern. No
|
||||
// permissions boundary: it is not a Lambda execution role.
|
||||
Sid: "CreateDeployRole",
|
||||
Effect: "Allow",
|
||||
Action: "iam:CreateRole",
|
||||
Resource: deployRole,
|
||||
Condition: { Null: { "iam:PermissionsBoundary": "true" } },
|
||||
},
|
||||
{
|
||||
Sid: "WriteDeployRole",
|
||||
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: "IamReadOnly",
|
||||
Effect: "Allow",
|
||||
Action: [
|
||||
"iam:GetOpenIDConnectProvider",
|
||||
"iam:GetPolicy",
|
||||
"iam:GetPolicyVersion",
|
||||
"iam:GetRole",
|
||||
"iam:GetRolePolicy",
|
||||
"iam:ListAttachedRolePolicies",
|
||||
"iam:ListInstanceProfilesForRole",
|
||||
"iam:ListPolicies",
|
||||
"iam:ListPolicyTags",
|
||||
"iam:ListPolicyVersions",
|
||||
"iam:ListRolePolicies",
|
||||
"iam:ListRoles",
|
||||
"iam:ListRoleTags",
|
||||
],
|
||||
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-*`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/** Ported from paychex-integrations terraform/hcp_iam.tf hcptf_apply_services plus the deploy contract. */
|
||||
function servicesPolicy(account: string, deployParams: string): object {
|
||||
const functions = `arn:aws:lambda:us-east-1:${account}:function:paychex-*`;
|
||||
const artifacts = `arn:aws:s3:::paychex-integrations-artifacts-${account}`;
|
||||
return {
|
||||
Version: "2012-10-17",
|
||||
Statement: [
|
||||
{
|
||||
Sid: "LambdaAll",
|
||||
Effect: "Allow",
|
||||
Action: "lambda:*",
|
||||
Resource: functions,
|
||||
},
|
||||
{
|
||||
Sid: "LambdaEventSourceMappingRead",
|
||||
Effect: "Allow",
|
||||
Action: [
|
||||
"lambda:GetEventSourceMapping",
|
||||
"lambda:ListTags",
|
||||
"lambda:TagResource",
|
||||
"lambda:UntagResource",
|
||||
],
|
||||
Resource: "*",
|
||||
},
|
||||
{
|
||||
Sid: "LambdaEventSourceMappings",
|
||||
Effect: "Allow",
|
||||
Action: [
|
||||
"lambda:CreateEventSourceMapping",
|
||||
"lambda:DeleteEventSourceMapping",
|
||||
"lambda:UpdateEventSourceMapping",
|
||||
],
|
||||
Resource: "*",
|
||||
Condition: { "ForAnyValue:StringLike": { "lambda:FunctionArn": functions } },
|
||||
},
|
||||
{
|
||||
Sid: "LambdaList",
|
||||
Effect: "Allow",
|
||||
Action: [
|
||||
"lambda:ListFunctions",
|
||||
"lambda:ListLayers",
|
||||
"lambda:ListEventSourceMappings",
|
||||
"lambda:GetAccountSettings",
|
||||
],
|
||||
Resource: "*",
|
||||
},
|
||||
{
|
||||
Sid: "CloudWatchLogs",
|
||||
Effect: "Allow",
|
||||
Action: [
|
||||
"logs:CreateLogGroup",
|
||||
"logs:DeleteLogGroup",
|
||||
"logs:PutRetentionPolicy",
|
||||
"logs:DeleteRetentionPolicy",
|
||||
"logs:TagResource",
|
||||
"logs:UntagResource",
|
||||
"logs:ListTagsForResource",
|
||||
],
|
||||
Resource: [
|
||||
`arn:aws:logs:us-east-1:${account}:log-group:/aws/lambda/paychex-*`,
|
||||
`arn:aws:logs:us-east-1:${account}:log-group:/aws/apigateway/paychex-webhooks`,
|
||||
`arn:aws:logs:us-east-1:${account}:log-group:/aws/apigateway/paychex-webhooks:*`,
|
||||
],
|
||||
},
|
||||
{
|
||||
Sid: "CloudWatchLogsDescribe",
|
||||
Effect: "Allow",
|
||||
Action: "logs:DescribeLogGroups",
|
||||
Resource: "*",
|
||||
},
|
||||
{
|
||||
// HTTP API access logging is delivered through CloudWatch vended logs.
|
||||
// None of these actions accept a resource ARN.
|
||||
Sid: "CloudWatchLogsDelivery",
|
||||
Effect: "Allow",
|
||||
Action: [
|
||||
"logs:CreateLogDelivery",
|
||||
"logs:GetLogDelivery",
|
||||
"logs:UpdateLogDelivery",
|
||||
"logs:DeleteLogDelivery",
|
||||
"logs:ListLogDeliveries",
|
||||
"logs:PutResourcePolicy",
|
||||
"logs:DescribeResourcePolicies",
|
||||
],
|
||||
Resource: "*",
|
||||
},
|
||||
{
|
||||
Sid: "LambdaArtifactsBucket",
|
||||
Effect: "Allow",
|
||||
Action: "s3:*",
|
||||
Resource: [artifacts, `${artifacts}/*`],
|
||||
},
|
||||
{
|
||||
Sid: "CloudWatchAlarms",
|
||||
Effect: "Allow",
|
||||
Action: "cloudwatch:*",
|
||||
Resource: `arn:aws:cloudwatch:us-east-1:${account}:alarm:paychex-*`,
|
||||
},
|
||||
{
|
||||
Sid: "SiteAlertsSns",
|
||||
Effect: "Allow",
|
||||
Action: ["sns:Publish", "sns:GetTopicAttributes"],
|
||||
Resource: `arn:aws:sns:us-east-1:${account}:site-alerts`,
|
||||
},
|
||||
{
|
||||
Sid: "PaychexSecretShell",
|
||||
Effect: "Allow",
|
||||
Action: [
|
||||
"secretsmanager:DeleteSecret",
|
||||
"secretsmanager:DescribeSecret",
|
||||
"secretsmanager:GetResourcePolicy",
|
||||
"secretsmanager:PutResourcePolicy",
|
||||
"secretsmanager:DeleteResourcePolicy",
|
||||
"secretsmanager:TagResource",
|
||||
"secretsmanager:UntagResource",
|
||||
],
|
||||
Resource: `arn:aws:secretsmanager:us-east-1:${account}:secret:paychex-integrations/*`,
|
||||
},
|
||||
{
|
||||
Sid: "PaychexSecretCreate",
|
||||
Effect: "Allow",
|
||||
Action: "secretsmanager:CreateSecret",
|
||||
Resource: "*",
|
||||
Condition: { StringLike: { "secretsmanager:Name": "paychex-integrations/*" } },
|
||||
},
|
||||
{
|
||||
Sid: "DynamoDBTable",
|
||||
Effect: "Allow",
|
||||
Action: "dynamodb:*",
|
||||
Resource: [
|
||||
`arn:aws:dynamodb:us-east-1:${account}:table/paychex-worker-ledger`,
|
||||
`arn:aws:dynamodb:us-east-1:${account}:table/paychex-worker-ledger/index/*`,
|
||||
`arn:aws:dynamodb:us-east-1:${account}:table/paychex-webhook-notifications`,
|
||||
`arn:aws:dynamodb:us-east-1:${account}:table/paychex-webhook-notifications/index/*`,
|
||||
`arn:aws:dynamodb:us-east-1:${account}:table/paychex-payroll-notices`,
|
||||
`arn:aws:dynamodb:us-east-1:${account}:table/paychex-payroll-notices/index/*`,
|
||||
`arn:aws:dynamodb:us-east-1:${account}:table/paychex-checkcomponents-posted`,
|
||||
`arn:aws:dynamodb:us-east-1:${account}:table/paychex-checkcomponents-posted/index/*`,
|
||||
`arn:aws:dynamodb:us-east-1:${account}:table/paychex-checkcomponents-period`,
|
||||
`arn:aws:dynamodb:us-east-1:${account}:table/paychex-checkcomponents-period/index/*`,
|
||||
],
|
||||
},
|
||||
{
|
||||
Sid: "DynamoDBList",
|
||||
Effect: "Allow",
|
||||
Action: "dynamodb:ListTables",
|
||||
Resource: "*",
|
||||
},
|
||||
{
|
||||
Sid: "SqsQueues",
|
||||
Effect: "Allow",
|
||||
Action: "sqs:*",
|
||||
Resource: queueArns(account),
|
||||
},
|
||||
{
|
||||
Sid: "SqsList",
|
||||
Effect: "Allow",
|
||||
Action: "sqs:ListQueues",
|
||||
Resource: "*",
|
||||
},
|
||||
{
|
||||
Sid: "HttpApi",
|
||||
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/*",
|
||||
],
|
||||
},
|
||||
{
|
||||
Sid: "PayrollSchedules",
|
||||
Effect: "Allow",
|
||||
Action: [
|
||||
"scheduler:CreateSchedule",
|
||||
"scheduler:UpdateSchedule",
|
||||
"scheduler:DeleteSchedule",
|
||||
"scheduler:GetSchedule",
|
||||
"scheduler:ListTagsForResource",
|
||||
"scheduler:TagResource",
|
||||
"scheduler:UntagResource",
|
||||
],
|
||||
Resource: scheduleArns(account),
|
||||
},
|
||||
{
|
||||
// Deploy contract read by the thin deploy.yaml caller.
|
||||
Sid: "WriteDeployContract",
|
||||
Effect: "Allow",
|
||||
Action: [
|
||||
"ssm:AddTagsToResource",
|
||||
"ssm:DeleteParameter",
|
||||
"ssm:GetParameter",
|
||||
"ssm:GetParameters",
|
||||
"ssm:ListTagsForResource",
|
||||
"ssm:PutParameter",
|
||||
"ssm:RemoveTagsFromResource",
|
||||
],
|
||||
Resource: deployParams,
|
||||
},
|
||||
{
|
||||
// DescribeParameters accepts only Resource "*".
|
||||
Sid: "DescribeParameters",
|
||||
Effect: "Allow",
|
||||
Action: "ssm:DescribeParameters",
|
||||
Resource: "*",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/** Ported from paychex-integrations terraform/hcp_iam.tf hcptf_plan_refresh plus the deploy role and contract. */
|
||||
function planPolicy(account: string, deployRole: string, deployParams: string): object {
|
||||
const artifacts = `arn:aws:s3:::paychex-integrations-artifacts-${account}`;
|
||||
return {
|
||||
Version: "2012-10-17",
|
||||
Statement: [
|
||||
{
|
||||
Sid: "RefreshIamRoles",
|
||||
Effect: "Allow",
|
||||
Action: [
|
||||
"iam:GetRole",
|
||||
"iam:GetRolePolicy",
|
||||
"iam:ListRolePolicies",
|
||||
"iam:ListAttachedRolePolicies",
|
||||
],
|
||||
Resource: [
|
||||
`arn:aws:iam::${account}:role/tf-managed/paychex-*`,
|
||||
deployRole,
|
||||
`arn:aws:iam::${account}:role/hcptf-paychex-integrations`,
|
||||
`arn:aws:iam::${account}:role/hcptf-paychex-integrations-plan`,
|
||||
],
|
||||
},
|
||||
{
|
||||
Sid: "RefreshGithubOidcProvider",
|
||||
Effect: "Allow",
|
||||
Action: "iam:GetOpenIDConnectProvider",
|
||||
Resource: `arn:aws:iam::${account}:oidc-provider/token.actions.githubusercontent.com`,
|
||||
},
|
||||
{
|
||||
Sid: "RefreshManagedPolicies",
|
||||
Effect: "Allow",
|
||||
Action: ["iam:GetPolicy", "iam:GetPolicyVersion"],
|
||||
Resource: "*",
|
||||
},
|
||||
{
|
||||
Sid: "RefreshLambda",
|
||||
Effect: "Allow",
|
||||
Action: "lambda:Get*",
|
||||
Resource: `arn:aws:lambda:us-east-1:${account}:function:paychex-*`,
|
||||
},
|
||||
{
|
||||
Sid: "RefreshLambdaList",
|
||||
Effect: "Allow",
|
||||
Action: [
|
||||
"lambda:ListFunctions",
|
||||
"lambda:ListEventSourceMappings",
|
||||
"lambda:GetEventSourceMapping",
|
||||
"lambda:GetAccountSettings",
|
||||
],
|
||||
Resource: "*",
|
||||
},
|
||||
{
|
||||
Sid: "RefreshArtifactsBucket",
|
||||
Effect: "Allow",
|
||||
Action: ["s3:Get*", "s3:ListBucket"],
|
||||
Resource: [artifacts, `${artifacts}/*`],
|
||||
},
|
||||
{
|
||||
Sid: "RefreshCloudWatchAlarms",
|
||||
Effect: "Allow",
|
||||
Action: ["cloudwatch:DescribeAlarms", "cloudwatch:ListTagsForResource"],
|
||||
Resource: `arn:aws:cloudwatch:us-east-1:${account}:alarm:paychex-*`,
|
||||
},
|
||||
{
|
||||
Sid: "RefreshLogs",
|
||||
Effect: "Allow",
|
||||
Action: ["logs:DescribeLogGroups", "logs:ListTagsForResource"],
|
||||
Resource: "*",
|
||||
},
|
||||
{
|
||||
Sid: "RefreshSecrets",
|
||||
Effect: "Allow",
|
||||
Action: [
|
||||
"secretsmanager:DescribeSecret",
|
||||
"secretsmanager:GetResourcePolicy",
|
||||
"secretsmanager:ListSecretVersionIds",
|
||||
],
|
||||
Resource: `arn:aws:secretsmanager:us-east-1:${account}:secret:paychex-integrations/*`,
|
||||
},
|
||||
{
|
||||
Sid: "RefreshDynamoDB",
|
||||
Effect: "Allow",
|
||||
Action: [
|
||||
"dynamodb:DescribeTable",
|
||||
"dynamodb:DescribeTimeToLive",
|
||||
"dynamodb:DescribeContinuousBackups",
|
||||
"dynamodb:ListTagsOfResource",
|
||||
],
|
||||
Resource: [
|
||||
`arn:aws:dynamodb:us-east-1:${account}:table/paychex-worker-ledger`,
|
||||
`arn:aws:dynamodb:us-east-1:${account}:table/paychex-webhook-notifications`,
|
||||
`arn:aws:dynamodb:us-east-1:${account}:table/paychex-payroll-notices`,
|
||||
`arn:aws:dynamodb:us-east-1:${account}:table/paychex-checkcomponents-posted`,
|
||||
`arn:aws:dynamodb:us-east-1:${account}:table/paychex-checkcomponents-period`,
|
||||
],
|
||||
},
|
||||
{
|
||||
Sid: "RefreshSqs",
|
||||
Effect: "Allow",
|
||||
Action: ["sqs:GetQueueAttributes", "sqs:GetQueueUrl", "sqs:ListQueueTags"],
|
||||
Resource: queueArns(account),
|
||||
},
|
||||
{
|
||||
Sid: "RefreshSqsList",
|
||||
Effect: "Allow",
|
||||
Action: "sqs:ListQueues",
|
||||
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: "RefreshPayrollSchedules",
|
||||
Effect: "Allow",
|
||||
Action: ["scheduler:GetSchedule", "scheduler:ListTagsForResource"],
|
||||
Resource: scheduleArns(account),
|
||||
},
|
||||
{
|
||||
Sid: "RefreshDeployContract",
|
||||
Effect: "Allow",
|
||||
Action: ["ssm:GetParameter", "ssm:GetParameters", "ssm:ListTagsForResource"],
|
||||
Resource: deployParams,
|
||||
},
|
||||
{
|
||||
// DescribeParameters accepts only Resource "*". The AWS provider
|
||||
// calls it while refreshing aws_ssm_parameter.
|
||||
Sid: "DescribeParameters",
|
||||
Effect: "Allow",
|
||||
Action: "ssm:DescribeParameters",
|
||||
Resource: "*",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function queueArns(account: string): string[] {
|
||||
return [
|
||||
"paychex-webhook-events",
|
||||
"paychex-webhook-events-dlq",
|
||||
"paychex-login-delay",
|
||||
"paychex-login-delay-dlq",
|
||||
"paychex-checkcomponents",
|
||||
"paychex-checkcomponents-dlq",
|
||||
"paychex-payroll-schedule",
|
||||
"paychex-payroll-schedule-dlq",
|
||||
].map((name) => `arn:aws:sqs:us-east-1:${account}:${name}`);
|
||||
}
|
||||
|
||||
function scheduleArns(account: string): string[] {
|
||||
return [
|
||||
`arn:aws:scheduler:us-east-1:${account}:schedule/default/paychex-payroll-monday`,
|
||||
`arn:aws:scheduler:us-east-1:${account}:schedule/default/paychex-payroll-thursday`,
|
||||
];
|
||||
}
|
||||
|
|
@ -3,12 +3,13 @@ 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 { 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 and mta-sts apply and plan roles. Dev is
|
||||
* 710827005802 and stays payments-dashboard only.
|
||||
* 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.
|
||||
|
|
@ -51,13 +52,28 @@ export interface SeahavenHcptfStackProps extends cdk.StackProps {
|
|||
siteImportExisting?: boolean;
|
||||
/** 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);
|
||||
if (props.importExisting && props.siteImportExisting) {
|
||||
throw new Error("hcptfPaymentsImport and hcptfSiteImport cannot both be set");
|
||||
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) {
|
||||
|
|
@ -68,7 +84,14 @@ export class SeahavenHcptfStack extends cdk.Stack {
|
|||
if (props.includeMtaSts) {
|
||||
new MtaStsRoles(this, "MtaSts");
|
||||
}
|
||||
cdk.Aspects.of(this).add(new HcptfPolicyAspect(props.importExisting === true));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue