mirror of
https://github.com/Sea-Haven-Industries/seahaven-org-baseline.git
synced 2026-09-30 04:33:15 +00:00
* feat(iam): add frontend Terraform substrate * fix(iam): align frontend Terraform substrate * feat(iam): enable frontend live Terraform roles
698 lines
21 KiB
TypeScript
698 lines
21 KiB
TypeScript
import * as cdk from "aws-cdk-lib";
|
|
import * as cfninc from "aws-cdk-lib/cloudformation-include";
|
|
import * as iam from "aws-cdk-lib/aws-iam";
|
|
import { CfnTag } from "aws-cdk-lib/core";
|
|
import { Construct } from "constructs";
|
|
|
|
const ACCOUNT_ID = "396287094661";
|
|
const CACHE_POLICY_ID = "658327ea-f89d-4fab-a63d-7e88639e58f6";
|
|
const SHARED_CERTIFICATE_ARN =
|
|
"arn:aws:acm:us-east-1:396287094661:certificate/2b78e74f-7b65-4b82-a413-7a498b102f00";
|
|
const EXECUTION_BOUNDARY_ARN =
|
|
"arn:aws:iam::396287094661:policy/external-dev-execution-boundary";
|
|
const HCP_PROVIDER_ARN =
|
|
"arn:aws:iam::396287094661:oidc-provider/app.terraform.io";
|
|
const GITHUB_PROVIDER_ARN =
|
|
"arn:aws:iam::396287094661:oidc-provider/token.actions.githubusercontent.com";
|
|
const FORBIDDEN_POC_IDENTIFIERS = new Set([
|
|
"E2CWLM1AFB964P",
|
|
"E30VSIK87N8H64",
|
|
"us-east-1shocfrontenddevSpaRewrite58674DB8",
|
|
"Z07671212N75U4YLPWZR8",
|
|
"E2JDVEZ6EGD49J",
|
|
"E1PF5R6QQNBZAI",
|
|
"us-east-1shocfrontendstagingSpaRewriteE9C0CBDA",
|
|
"Z02602739VQWBWCAGXP4",
|
|
"Z02451891BSZD93CMMGDU",
|
|
SHARED_CERTIFICATE_ARN,
|
|
]);
|
|
|
|
interface FrontendEnvironment {
|
|
readonly key: "tf-poc" | "dev" | "staging";
|
|
readonly workspace: string;
|
|
readonly bucketName: string;
|
|
readonly domainName: string;
|
|
readonly hostedZoneId: string;
|
|
readonly certificateArn: string;
|
|
readonly deployRoleName: string;
|
|
readonly distributionId: string;
|
|
readonly originAccessControlId: string;
|
|
readonly functionName: string;
|
|
readonly roleCondition: cdk.CfnCondition;
|
|
readonly invalidationCondition?: cdk.CfnCondition;
|
|
}
|
|
|
|
interface ShocFrontendResourcesProps {
|
|
readonly template: cfninc.CfnInclude;
|
|
readonly enablePocRoles: boolean;
|
|
readonly enableLiveRoles: boolean;
|
|
readonly pocDistributionId: string;
|
|
readonly pocOriginAccessControlId: string;
|
|
readonly pocFunctionName: string;
|
|
readonly pocHostedZoneId: string;
|
|
readonly pocCertificateArn: string;
|
|
}
|
|
|
|
const retain = (resource: cdk.CfnResource): void => {
|
|
resource.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.RETAIN;
|
|
resource.cfnOptions.updateReplacePolicy = cdk.CfnDeletionPolicy.RETAIN;
|
|
};
|
|
|
|
const roleArn = (roleName: string): string =>
|
|
`arn:aws:iam::${ACCOUNT_ID}:role/${roleName}`;
|
|
|
|
const bucketArn = (bucketName: string): string => `arn:aws:s3:::${bucketName}`;
|
|
|
|
const distributionArn = (distributionId: string): string =>
|
|
`arn:aws:cloudfront::${ACCOUNT_ID}:distribution/${distributionId}`;
|
|
|
|
const functionArn = (functionName: string): string =>
|
|
`arn:aws:cloudfront::${ACCOUNT_ID}:function/${functionName}`;
|
|
|
|
const originAccessControlArn = (originAccessControlId: string): string =>
|
|
`arn:aws:cloudfront::${ACCOUNT_ID}:origin-access-control/${originAccessControlId}`;
|
|
|
|
const hostedZoneArn = (hostedZoneId: string): string =>
|
|
`arn:aws:route53:::hostedzone/${hostedZoneId}`;
|
|
|
|
const frontendReadPolicy = (
|
|
environment: FrontendEnvironment,
|
|
): Record<string, unknown> => {
|
|
const siteBucketArn = bucketArn(environment.bucketName);
|
|
return {
|
|
Version: "2012-10-17",
|
|
Statement: [
|
|
{
|
|
Sid: "CallerIdentity",
|
|
Effect: "Allow",
|
|
Action: "sts:GetCallerIdentity",
|
|
Resource: "*",
|
|
},
|
|
{
|
|
Sid: "ReadExactSiteBucket",
|
|
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: siteBucketArn,
|
|
},
|
|
{
|
|
Sid: "ReadExactCloudFrontResources",
|
|
Effect: "Allow",
|
|
Action: [
|
|
"cloudfront:DescribeFunction",
|
|
"cloudfront:GetDistribution",
|
|
"cloudfront:GetDistributionConfig",
|
|
"cloudfront:GetFunction",
|
|
"cloudfront:GetOriginAccessControl",
|
|
"cloudfront:ListTagsForResource",
|
|
],
|
|
Resource: [
|
|
distributionArn(environment.distributionId),
|
|
functionArn(environment.functionName),
|
|
originAccessControlArn(environment.originAccessControlId),
|
|
],
|
|
},
|
|
{
|
|
Sid: "ListCloudFrontInventory",
|
|
Effect: "Allow",
|
|
Action: [
|
|
"cloudfront:ListDistributions",
|
|
"cloudfront:ListFunctions",
|
|
"cloudfront:ListOriginAccessControls",
|
|
],
|
|
Resource: "*",
|
|
},
|
|
{
|
|
Sid: "ReadManagedCachePolicy",
|
|
Effect: "Allow",
|
|
Action: "cloudfront:GetCachePolicy",
|
|
Resource: `arn:aws:cloudfront::${ACCOUNT_ID}:cache-policy/${CACHE_POLICY_ID}`,
|
|
},
|
|
{
|
|
Sid: "ListCachePolicies",
|
|
Effect: "Allow",
|
|
Action: "cloudfront:ListCachePolicies",
|
|
Resource: "*",
|
|
},
|
|
{
|
|
Sid: "ReadExactDeployRole",
|
|
Effect: "Allow",
|
|
Action: [
|
|
"iam:GetRole",
|
|
"iam:GetRolePolicy",
|
|
"iam:ListAttachedRolePolicies",
|
|
"iam:ListRolePolicies",
|
|
"iam:ListRoleTags",
|
|
],
|
|
Resource: roleArn(environment.deployRoleName),
|
|
},
|
|
{
|
|
Sid: "ReadGithubOidcProvider",
|
|
Effect: "Allow",
|
|
Action: "iam:GetOpenIDConnectProvider",
|
|
Resource: GITHUB_PROVIDER_ARN,
|
|
},
|
|
{
|
|
Sid: "ListOidcProviders",
|
|
Effect: "Allow",
|
|
Action: "iam:ListOpenIDConnectProviders",
|
|
Resource: "*",
|
|
},
|
|
{
|
|
Sid: "ReadExactCertificate",
|
|
Effect: "Allow",
|
|
Action: [
|
|
"acm:DescribeCertificate",
|
|
"acm:GetCertificate",
|
|
"acm:ListTagsForCertificate",
|
|
],
|
|
Resource: environment.certificateArn,
|
|
},
|
|
{
|
|
Sid: "ListCertificates",
|
|
Effect: "Allow",
|
|
Action: "acm:ListCertificates",
|
|
Resource: "*",
|
|
},
|
|
{
|
|
Sid: "ReadExactDns",
|
|
Effect: "Allow",
|
|
Action: [
|
|
"route53:GetHostedZone",
|
|
"route53:ListResourceRecordSets",
|
|
"route53:ListTagsForResource",
|
|
],
|
|
Resource: hostedZoneArn(environment.hostedZoneId),
|
|
},
|
|
{
|
|
Sid: "FindHostedZone",
|
|
Effect: "Allow",
|
|
Action: ["route53:ListHostedZones", "route53:ListHostedZonesByName"],
|
|
Resource: "*",
|
|
},
|
|
{
|
|
Sid: "ReadDnsChanges",
|
|
Effect: "Allow",
|
|
Action: "route53:GetChange",
|
|
Resource: "arn:aws:route53:::change/*",
|
|
},
|
|
],
|
|
};
|
|
};
|
|
|
|
const frontendApplyPolicy = (
|
|
environment: FrontendEnvironment,
|
|
): Record<string, unknown> => ({
|
|
Version: "2012-10-17",
|
|
Statement: [
|
|
{
|
|
Sid: "DenyRoleLifecycleAndTrustMutation",
|
|
Effect: "Deny",
|
|
Action: [
|
|
"iam:AttachRolePolicy",
|
|
"iam:CreateRole",
|
|
"iam:CreateServiceLinkedRole",
|
|
"iam:DeleteRole",
|
|
"iam:DeleteRolePermissionsBoundary",
|
|
"iam:DeleteRolePolicy",
|
|
"iam:DetachRolePolicy",
|
|
"iam:PassRole",
|
|
"iam:PutRolePermissionsBoundary",
|
|
"iam:UpdateAssumeRolePolicy",
|
|
"iam:UpdateRole",
|
|
"iam:UpdateRoleDescription",
|
|
],
|
|
Resource: "*",
|
|
},
|
|
{
|
|
Sid: "DenyManagedPolicyMutation",
|
|
Effect: "Deny",
|
|
Action: [
|
|
"iam:CreatePolicy",
|
|
"iam:CreatePolicyVersion",
|
|
"iam:DeletePolicy",
|
|
"iam:DeletePolicyVersion",
|
|
"iam:SetDefaultPolicyVersion",
|
|
],
|
|
Resource: "*",
|
|
},
|
|
{
|
|
Sid: "DenyInfrastructureReplacement",
|
|
Effect: "Deny",
|
|
Action: [
|
|
"cloudfront:CreateDistribution",
|
|
"cloudfront:CreateFunction",
|
|
"cloudfront:CreateOriginAccessControl",
|
|
"cloudfront:DeleteDistribution",
|
|
"cloudfront:DeleteFunction",
|
|
"cloudfront:DeleteOriginAccessControl",
|
|
"cloudfront:PublishFunction",
|
|
"cloudfront:UpdateDistribution",
|
|
"cloudfront:UpdateFunction",
|
|
"cloudfront:UpdateOriginAccessControl",
|
|
"s3:CreateBucket",
|
|
"s3:DeleteBucket",
|
|
"s3:DeleteBucketEncryption",
|
|
"s3:DeleteBucketOwnershipControls",
|
|
"s3:DeleteBucketPolicy",
|
|
"s3:DeleteBucketPublicAccessBlock",
|
|
"s3:PutBucketOwnershipControls",
|
|
"s3:PutBucketPublicAccessBlock",
|
|
"s3:PutBucketVersioning",
|
|
"s3:PutEncryptionConfiguration",
|
|
],
|
|
Resource: "*",
|
|
},
|
|
{
|
|
Sid: "DenySecretAccess",
|
|
Effect: "Deny",
|
|
Action: [
|
|
"kms:Decrypt",
|
|
"secretsmanager:*",
|
|
"ssm:GetParameter",
|
|
"ssm:GetParameters",
|
|
"ssm:GetParametersByPath",
|
|
],
|
|
Resource: "*",
|
|
},
|
|
{
|
|
Sid: "LockHcpTerraformWorkspaceTag",
|
|
Effect: "Deny",
|
|
Action: ["iam:TagRole", "iam:UntagRole"],
|
|
Resource: "*",
|
|
Condition: {
|
|
"ForAnyValue:StringEquals": {
|
|
"aws:TagKeys": "HcpTerraformWorkspace",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Sid: "TagExactSiteBucket",
|
|
Effect: "Allow",
|
|
Action: "s3:PutBucketTagging",
|
|
Resource: bucketArn(environment.bucketName),
|
|
},
|
|
{
|
|
Sid: "ReplaceExactBucketPolicy",
|
|
Effect: "Allow",
|
|
Action: "s3:PutBucketPolicy",
|
|
Resource: bucketArn(environment.bucketName),
|
|
},
|
|
{
|
|
Sid: "TagExactCloudFrontResources",
|
|
Effect: "Allow",
|
|
Action: ["cloudfront:TagResource", "cloudfront:UntagResource"],
|
|
Resource: [
|
|
distributionArn(environment.distributionId),
|
|
functionArn(environment.functionName),
|
|
],
|
|
},
|
|
{
|
|
Sid: "ReplaceExactDeployInlinePolicy",
|
|
Effect: "Allow",
|
|
Action: "iam:PutRolePolicy",
|
|
Resource: roleArn(environment.deployRoleName),
|
|
Condition: {
|
|
StringEquals: {
|
|
"iam:PermissionsBoundary": `arn:aws:iam::${ACCOUNT_ID}:policy/shoc-frontend-new-${environment.key}-deploy-boundary`,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Sid: "TagExactDeployRole",
|
|
Effect: "Allow",
|
|
Action: ["iam:TagRole", "iam:UntagRole"],
|
|
Resource: roleArn(environment.deployRoleName),
|
|
},
|
|
{
|
|
Sid: "ChangeExactSiteAliases",
|
|
Effect: "Allow",
|
|
Action: "route53:ChangeResourceRecordSets",
|
|
Resource: hostedZoneArn(environment.hostedZoneId),
|
|
Condition: {
|
|
"ForAllValues:StringEquals": {
|
|
"route53:ChangeResourceRecordSetsActions": [
|
|
"CREATE",
|
|
"DELETE",
|
|
"UPSERT",
|
|
],
|
|
"route53:ChangeResourceRecordSetsNormalizedRecordNames": [
|
|
environment.domainName,
|
|
],
|
|
"route53:ChangeResourceRecordSetsRecordTypes": ["A", "AAAA"],
|
|
},
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
const assumeRolePolicy = (
|
|
workspace: string,
|
|
runPhase: "plan" | "apply",
|
|
): Record<string, unknown> => ({
|
|
Version: "2012-10-17",
|
|
Statement: [
|
|
{
|
|
Effect: "Allow",
|
|
Principal: { Federated: HCP_PROVIDER_ARN },
|
|
Action: "sts:AssumeRoleWithWebIdentity",
|
|
Condition: {
|
|
StringEquals: {
|
|
"app.terraform.io:aud": "aws.workload.identity",
|
|
"app.terraform.io:sub":
|
|
`organization:seahaven:project:seahaven-external-dev:` +
|
|
`workspace:${workspace}:run_phase:${runPhase}`,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
const roleTags = (
|
|
environment: FrontendEnvironment,
|
|
includeManagerTag: boolean,
|
|
): CfnTag[] => {
|
|
const tags = [
|
|
{ key: "Environment", value: environment.key },
|
|
{ key: "Workspace", value: environment.workspace },
|
|
];
|
|
if (includeManagerTag) {
|
|
tags.push({
|
|
key: "HcpTerraformWorkspace",
|
|
value: environment.workspace,
|
|
});
|
|
}
|
|
return tags;
|
|
};
|
|
|
|
export class ShocFrontendResources extends Construct {
|
|
constructor(scope: Construct, id: string, props: ShocFrontendResourcesProps) {
|
|
super(scope, id);
|
|
|
|
this.validatePocIdentifiers(props);
|
|
|
|
const pocInvalidationCondition = new cdk.CfnCondition(
|
|
this,
|
|
"HasShocFrontendPocDistribution",
|
|
{
|
|
expression: cdk.Fn.conditionNot(
|
|
cdk.Fn.conditionEquals(props.pocDistributionId, ""),
|
|
),
|
|
},
|
|
);
|
|
pocInvalidationCondition.overrideLogicalId(
|
|
"HasShocFrontendPocDistribution",
|
|
);
|
|
const pocRoleCondition = new cdk.CfnCondition(
|
|
this,
|
|
"ShouldManageShocFrontendPocRoles",
|
|
{
|
|
expression: cdk.Fn.conditionAnd(
|
|
cdk.Fn.conditionEquals(cdk.Aws.ACCOUNT_ID, ACCOUNT_ID),
|
|
cdk.Fn.conditionEquals(
|
|
props.enablePocRoles ? "true" : "false",
|
|
"true",
|
|
),
|
|
cdk.Fn.conditionNot(
|
|
cdk.Fn.conditionEquals(props.pocDistributionId, ""),
|
|
),
|
|
cdk.Fn.conditionNot(
|
|
cdk.Fn.conditionEquals(props.pocOriginAccessControlId, ""),
|
|
),
|
|
cdk.Fn.conditionNot(
|
|
cdk.Fn.conditionEquals(props.pocFunctionName, ""),
|
|
),
|
|
cdk.Fn.conditionNot(
|
|
cdk.Fn.conditionEquals(props.pocHostedZoneId, ""),
|
|
),
|
|
cdk.Fn.conditionNot(
|
|
cdk.Fn.conditionEquals(props.pocCertificateArn, ""),
|
|
),
|
|
),
|
|
},
|
|
);
|
|
pocRoleCondition.overrideLogicalId("ShouldManageShocFrontendPocRoles");
|
|
const liveRoleCondition = new cdk.CfnCondition(
|
|
this,
|
|
"ShouldManageShocFrontendLiveRoles",
|
|
{
|
|
expression: cdk.Fn.conditionAnd(
|
|
cdk.Fn.conditionEquals(cdk.Aws.ACCOUNT_ID, ACCOUNT_ID),
|
|
cdk.Fn.conditionEquals(
|
|
props.enableLiveRoles ? "true" : "false",
|
|
"true",
|
|
),
|
|
),
|
|
},
|
|
);
|
|
liveRoleCondition.overrideLogicalId("ShouldManageShocFrontendLiveRoles");
|
|
const externalDevCondition = props.template.getCondition(
|
|
"IsExternalDevAccount",
|
|
);
|
|
|
|
const environments: FrontendEnvironment[] = [
|
|
{
|
|
key: "tf-poc",
|
|
workspace: "shoc-frontend-new-tf-poc",
|
|
bucketName: "seahaven-shoc-frontend-tf-poc",
|
|
domainName: "frontend-tf-poc.seahaven.com",
|
|
hostedZoneId: props.pocHostedZoneId,
|
|
certificateArn: props.pocCertificateArn,
|
|
deployRoleName: "githubdeploy-shoc-frontend-new-tf-poc",
|
|
distributionId: props.pocDistributionId,
|
|
originAccessControlId: props.pocOriginAccessControlId,
|
|
functionName: props.pocFunctionName,
|
|
roleCondition: pocRoleCondition,
|
|
invalidationCondition: pocInvalidationCondition,
|
|
},
|
|
{
|
|
key: "dev",
|
|
workspace: "shoc-frontend-new-dev",
|
|
bucketName: "seahaven-shoc-frontend-dev",
|
|
domainName: "dev.seahaven.com",
|
|
hostedZoneId: "Z07671212N75U4YLPWZR8",
|
|
certificateArn: SHARED_CERTIFICATE_ARN,
|
|
deployRoleName: "githubdeploy-shoc-frontend-new-dev",
|
|
distributionId: "E2CWLM1AFB964P",
|
|
originAccessControlId: "E30VSIK87N8H64",
|
|
functionName: "us-east-1shocfrontenddevSpaRewrite58674DB8",
|
|
roleCondition: liveRoleCondition,
|
|
},
|
|
{
|
|
key: "staging",
|
|
workspace: "shoc-frontend-new-staging",
|
|
bucketName: "seahaven-shoc-frontend-staging",
|
|
domainName: "staging.seahaven.com",
|
|
hostedZoneId: "Z02602739VQWBWCAGXP4",
|
|
certificateArn: SHARED_CERTIFICATE_ARN,
|
|
deployRoleName: "githubdeploy-shoc-frontend-new-staging",
|
|
distributionId: "E2JDVEZ6EGD49J",
|
|
originAccessControlId: "E1PF5R6QQNBZAI",
|
|
functionName: "us-east-1shocfrontendstagingSpaRewriteE9C0CBDA",
|
|
roleCondition: liveRoleCondition,
|
|
},
|
|
];
|
|
|
|
for (const environment of environments) {
|
|
this.addEnvironment(environment, externalDevCondition);
|
|
}
|
|
}
|
|
|
|
private validatePocIdentifiers(props: ShocFrontendResourcesProps): void {
|
|
const distributionPattern = /^E[A-Z0-9]+$/;
|
|
const functionPattern = /^[A-Za-z0-9_-]+$/;
|
|
const hostedZonePattern = /^Z[A-Z0-9]+$/;
|
|
const certificatePattern =
|
|
/^arn:aws:acm:us-east-1:396287094661:certificate\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
const identifiers = [
|
|
props.pocDistributionId,
|
|
props.pocOriginAccessControlId,
|
|
props.pocFunctionName,
|
|
props.pocHostedZoneId,
|
|
props.pocCertificateArn,
|
|
];
|
|
const hasPartialIdentifiers =
|
|
identifiers.some((value) => value !== "") &&
|
|
identifiers.some((value) => value === "");
|
|
if (hasPartialIdentifiers) {
|
|
throw new Error(
|
|
"All five shocFrontendPoc identifiers must be set together",
|
|
);
|
|
}
|
|
if (
|
|
props.pocDistributionId !== "" &&
|
|
(!distributionPattern.test(props.pocDistributionId) ||
|
|
!distributionPattern.test(props.pocOriginAccessControlId) ||
|
|
!functionPattern.test(props.pocFunctionName) ||
|
|
!hostedZonePattern.test(props.pocHostedZoneId) ||
|
|
!certificatePattern.test(props.pocCertificateArn))
|
|
) {
|
|
throw new Error("Invalid shocFrontendPoc identifier");
|
|
}
|
|
if (
|
|
identifiers.some((identifier) =>
|
|
FORBIDDEN_POC_IDENTIFIERS.has(identifier),
|
|
)
|
|
) {
|
|
throw new Error(
|
|
"shocFrontendPoc identifiers must not reuse live frontend or backend tf-poc resources",
|
|
);
|
|
}
|
|
if (props.enablePocRoles && props.pocDistributionId === "") {
|
|
throw new Error(
|
|
"enableShocFrontendPocRoles requires all five identifiers",
|
|
);
|
|
}
|
|
}
|
|
|
|
private addEnvironment(
|
|
environment: FrontendEnvironment,
|
|
externalDevCondition: cdk.CfnCondition,
|
|
): void {
|
|
const logicalSuffix =
|
|
environment.key === "tf-poc"
|
|
? "Poc"
|
|
: environment.key.charAt(0).toUpperCase() + environment.key.slice(1);
|
|
const siteBucketArn = bucketArn(environment.bucketName);
|
|
const exactDistributionArn = distributionArn(environment.distributionId);
|
|
const boundaryStatements: unknown[] = [
|
|
{
|
|
Sid: "ReadDeploymentBucket",
|
|
Effect: "Allow",
|
|
Action: [
|
|
"s3:GetBucketLocation",
|
|
"s3:GetBucketVersioning",
|
|
"s3:ListBucket",
|
|
"s3:ListBucketVersions",
|
|
],
|
|
Resource: siteBucketArn,
|
|
},
|
|
{
|
|
Sid: "PublishRollbackAndPruneSiteObjects",
|
|
Effect: "Allow",
|
|
Action: [
|
|
"s3:DeleteObject",
|
|
"s3:DeleteObjectVersion",
|
|
"s3:GetObject",
|
|
"s3:GetObjectVersion",
|
|
"s3:PutObject",
|
|
],
|
|
Resource: `${siteBucketArn}/*`,
|
|
},
|
|
];
|
|
const invalidationStatement = {
|
|
Sid: "InvalidateExactDistribution",
|
|
Effect: "Allow",
|
|
Action: ["cloudfront:CreateInvalidation", "cloudfront:GetInvalidation"],
|
|
Resource: exactDistributionArn,
|
|
};
|
|
boundaryStatements.push(
|
|
environment.invalidationCondition === undefined
|
|
? invalidationStatement
|
|
: cdk.Fn.conditionIf(
|
|
environment.invalidationCondition.logicalId,
|
|
invalidationStatement,
|
|
cdk.Aws.NO_VALUE,
|
|
),
|
|
);
|
|
|
|
const deployBoundary = new iam.CfnManagedPolicy(
|
|
this,
|
|
`ShocFrontend${logicalSuffix}DeployBoundary`,
|
|
{
|
|
managedPolicyName: `shoc-frontend-new-${environment.key}-deploy-boundary`,
|
|
description:
|
|
`Maximum content deployment permissions for ` +
|
|
`${environment.deployRoleName}.`,
|
|
policyDocument: {
|
|
Version: "2012-10-17",
|
|
Statement: boundaryStatements,
|
|
},
|
|
},
|
|
);
|
|
deployBoundary.cfnOptions.condition = externalDevCondition;
|
|
deployBoundary.overrideLogicalId(
|
|
`ShocFrontend${logicalSuffix}DeployBoundary`,
|
|
);
|
|
retain(deployBoundary);
|
|
|
|
const planRole = new iam.CfnRole(
|
|
this,
|
|
`HcptfShocFrontend${logicalSuffix}PlanRole`,
|
|
{
|
|
roleName: `${environment.workspace}-plan`.replace(
|
|
"shoc-frontend-new",
|
|
"hcptf-shoc-frontend-new",
|
|
),
|
|
description: `Read-only HCP Terraform plan role for ${environment.workspace}.`,
|
|
permissionsBoundary: EXECUTION_BOUNDARY_ARN,
|
|
maxSessionDuration: 3600,
|
|
assumeRolePolicyDocument: assumeRolePolicy(
|
|
environment.workspace,
|
|
"plan",
|
|
),
|
|
policies: [
|
|
{
|
|
policyName: `${environment.workspace}-import-read`,
|
|
policyDocument: frontendReadPolicy(environment),
|
|
},
|
|
],
|
|
tags: roleTags(environment, false),
|
|
},
|
|
);
|
|
planRole.cfnOptions.condition = environment.roleCondition;
|
|
planRole.overrideLogicalId(`HcptfShocFrontend${logicalSuffix}PlanRole`);
|
|
retain(planRole);
|
|
|
|
const applyRole = new iam.CfnRole(
|
|
this,
|
|
`HcptfShocFrontend${logicalSuffix}ApplyRole`,
|
|
{
|
|
roleName: environment.workspace.replace(
|
|
"shoc-frontend-new",
|
|
"hcptf-shoc-frontend-new",
|
|
),
|
|
description: `Constrained HCP Terraform apply role for ${environment.workspace}.`,
|
|
permissionsBoundary: EXECUTION_BOUNDARY_ARN,
|
|
maxSessionDuration: 3600,
|
|
assumeRolePolicyDocument: assumeRolePolicy(
|
|
environment.workspace,
|
|
"apply",
|
|
),
|
|
policies: [
|
|
{
|
|
policyName: `${environment.workspace}-import-read`,
|
|
policyDocument: frontendReadPolicy(environment),
|
|
},
|
|
{
|
|
policyName: `${environment.workspace}-import-apply`,
|
|
policyDocument: frontendApplyPolicy(environment),
|
|
},
|
|
],
|
|
tags: roleTags(environment, true),
|
|
},
|
|
);
|
|
applyRole.cfnOptions.condition = environment.roleCondition;
|
|
applyRole.overrideLogicalId(`HcptfShocFrontend${logicalSuffix}ApplyRole`);
|
|
applyRole.addResourceDependency(deployBoundary);
|
|
retain(applyRole);
|
|
}
|
|
}
|